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:
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):
Resposta — cancelamento¶
Resposta — 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:
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