Pular para conteúdo

Webhooks

POST /webhooks/eduzz

Recebe eventos de pagamento da Eduzz e provisiona acesso automaticamente.

Configuração

No painel Eduzz (Desenvolvedor → Webhooks):

URL:        https://api.quantfx.com.br/webhooks/eduzz
Eventos:    myeduzz.invoice_paid
            myeduzz.invoice_canceled
            myeduzz.invoice_refunded
            myeduzz.invoice_chargeback

Copiar "Secret Key" gerado → setar em .env:

EDUZZ_SECRET=segredo-gerado-pela-eduzz

Validação HMAC

Eduzz envia signature no header x-signature:

expected = hmac.new(EDUZZ_SECRET.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature.lower()):
    return HTTPException(403)

Fail-closed: se EDUZZ_SECRET estiver vazio, webhook rejeita 403 (nunca aceita sem secret).

Payload Eduzz (pagamento aprovado)

{
  "event": "myeduzz.invoice_paid",
  "data": {
    "id":     "INV-12345",
    "status": "paid",
    "items": [
      {
        "productId": "3031361",
        "name":      "QuantFX EA Basic — Mensal",
        "quantity":  1,
        "amount":    197.00
      }
    ],
    "buyer": {
      "name":  "João Silva",
      "email": "joao@exemplo.com"
    },
    "paid_at": "2026-05-27T14:00:00Z"
  }
}

Mapping de produtos (webhooks.py:PRODUCT_MAP)

productId Plano
3031067 telegram (pai)
3031357 telegram mensal
3031358 telegram único
3031074 basic (pai)
3031361 basic mensal
3031365 basic único
3031078 pro (pai)
3031367 pro mensal
3031368 pro único

Fallback se productId não mapeado: detecta "pro" ou "basic" no nome.

Resposta — pagamento novo

{
  "ok": true,
  "action": "api_key_created",
  "plan": "pro",
  "key_id": 42,
  "expires": "2026-06-27T00:00:00Z",
  "email_sent": true
}

Resposta — idempotência

Se já existe chave ativa para o email (cliente comprou de novo antes do refresh):

{
  "ok": true,
  "action": "already_provisioned",
  "plan": "pro",
  "key_id": 42
}

Resposta — cancelamento

{
  "ok": true,
  "action": "access_revoked",
  "event": "myeduzz.invoice_canceled",
  "keys_revoked": 1
}

Resposta — assinatura inválida

HTTP/1.1 403 Forbidden
{"detail": "Assinatura inválida."}

Eduzz tentará reenviar com backoff. Se você acabou de mudar EDUZZ_SECRET, os payloads antigos vão falhar (esperado).

Fluxo end-to-end

sequenceDiagram
    participant Eduzz
    participant API as /webhooks/eduzz
    participant DB
    participant SMTP
    participant Cliente

    Eduzz->>API: POST + x-signature
    API->>API: HMAC validate
    API->>API: parse event
    alt event = invoice_paid + status = paid
        API->>DB: SELECT existing key by email
        alt já tem ativa
            API-->>Eduzz: 200 already_provisioned
        else nova
            API->>DB: INSERT api_keys (gen_random_uuid)
            API->>SMTP: send email HTML (background task)
            API-->>Eduzz: 200 api_key_created
            SMTP->>Cliente: Email com chave + tutorial
        end
    else event = canceled | refunded | chargeback
        API->>DB: UPDATE api_keys SET active=FALSE
        API->>SMTP: send email "acesso revogado"
        API-->>Eduzz: 200 access_revoked
    else outros eventos
        API-->>Eduzz: 200 ignored
    end

Privacidade nos logs

Email nunca aparece em texto puro nos logs:

eh = f"{hash(email) & 0xFFFF:04x}"   # hash 4 chars
log.info(f"Eduzz webhook | email_hash={eh}")

Testes

Teste manual de provisionamento:

# Calcule HMAC do payload com o secret
SECRET="seu-secret-eduzz"
PAYLOAD='{"event":"myeduzz.invoice_paid","data":{"status":"paid","items":[{"productId":"3031367","name":"Pro"}],"buyer":{"name":"Teste","email":"teste@example.com"}}}'
SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X POST https://api.quantfx.com.br/webhooks/eduzz \
  -H "x-signature: $SIG" \
  -H "Content-Type: application/json" \
  -d "$PAYLOAD"

Testes automatizados em tests/test_webhooks.py.

Por que não usamos Stripe (ainda)

Ver ADR-005. TL;DR:

  • Eduzz suporta PIX nativo (Stripe não no Brasil para PJ pequena)
  • Tributação brasileira simplificada (DARF MEI/Simples)
  • Webhook similar (HMAC-SHA256)
  • Taxa: ~10% Eduzz vs 3-7% Stripe — mas Stripe exige conta internacional