Configuração de Webhooks
Instruções para configurar e testar o webhook do ASAAS em sandbox e produção.
Visão Geral
O ASAAS usa webhooks para notificar o sistema sobre eventos de pagamento assíncronos.
O endpoint no fast_deliv é: POST /api/v1/webhooks/asaas
ASAAS (evento de transferência)
│
│ POST /api/v1/webhooks/asaas
│ Header: asaas-signature: <hmac-sha256>
▼
Backend FastAPI
│
│ 1. Valida HMAC
│ 2. Identifica evento (TRANSFER_DONE, TRANSFER_FAILED, etc.)
│ 3. Atualiza withdrawal.status no Supabase
▼
Supabase (withdrawal atualizado)
│
│ Realtime broadcast
▼
Driver App (status do saque atualizado)
Configuração no ASAAS Sandbox
1. Acessar configurações de webhook
- Entre em sandbox.asaas.com
- Vá em Configurações > Integrações > Webhooks
- Clique em Adicionar webhook
2. Configurar o webhook
| Campo | Valor |
|---|---|
| URL | https://fast-deliv-backend.vercel.app/api/v1/webhooks/asaas |
| Versão | v3 |
| Habilitado | Sim |
| Eventos | Selecione todos os eventos de transferência |
Eventos relevantes:
- TRANSFER_DONE
- TRANSFER_APPROVED
- TRANSFER_FAILED
- TRANSFER_CANCELLED
3. Copiar o Token/Secret
Após criar o webhook, copie o Token de autenticação gerado pelo ASAAS.
Este valor vai para ASAAS_WEBHOOK_SECRET no backend.
Configuração em Produção
O processo é idêntico ao sandbox, mas:
- URL: https://fast-deliv-backend.vercel.app/api/v1/webhooks/asaas
- Dashboard: asaas.com (não sandbox)
- Gera um novo secret diferente do sandbox
Implementação da Validação
# backend/app/api/v1/webhooks.py
import hashlib
import hmac
@router.post("/asaas", status_code=status.HTTP_200_OK)
async def asaas_webhook(
request: Request,
asaas_signature: str | None = Header(default=None, alias="asaas-signature"),
) -> dict[str, str]:
body = await request.body()
# Validar assinatura HMAC-SHA256
if asaas_signature:
expected = hmac.new(
settings.asaas_webhook_secret.encode(),
body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, asaas_signature):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Assinatura inválida",
)
payload = await request.json()
event: str = payload.get("event", "")
transfer_data: dict = payload.get("transfer", {})
transfer_id = str(transfer_data.get("id", ""))
if event in ("TRANSFER_DONE", "TRANSFER_APPROVED"):
supabase.table("withdrawals").update({"status": "completed"}).eq(
"asaas_transfer_id", transfer_id
).execute()
elif event in ("TRANSFER_FAILED", "TRANSFER_CANCELLED"):
supabase.table("withdrawals").update({
"status": "failed",
"error_message": event,
}).eq("asaas_transfer_id", transfer_id).execute()
return {"status": "ok"}
Testando Webhooks Localmente
Para testar webhooks em desenvolvimento, use ngrok ou Cloudflare Tunnel para expor o servidor local.
Com ngrok
# Instalar ngrok
npm install -g ngrok
# Em um terminal: rodar o backend
uv run uvicorn app.main:app --reload --port 8000
# Em outro terminal: criar túnel
ngrok http 8000
# Saída:
# Forwarding https://abc123.ngrok.io -> http://localhost:8000
Configure o webhook ASAAS sandbox com a URL ngrok:
Com Cloudflare Tunnel (alternativa gratuita)
# Instalar cloudflared
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb
# Criar túnel temporário
cloudflared tunnel --url http://localhost:8000
Simulando Webhooks Manualmente
Para testar o endpoint sem configurar o ASAAS:
# Gerar assinatura HMAC correta
WEBHOOK_SECRET="seu_webhook_secret_aqui"
BODY='{"event":"TRANSFER_DONE","transfer":{"id":"tra_000012345","value":100.00,"status":"DONE"}}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
# Enviar requisição
curl -X POST http://localhost:8000/api/v1/webhooks/asaas \
-H "Content-Type: application/json" \
-H "asaas-signature: $SIGNATURE" \
-d "$BODY"
# Resposta esperada:
# {"status": "ok"}
Simulando falha
BODY='{"event":"TRANSFER_FAILED","transfer":{"id":"tra_000012345"}}'
SIGNATURE=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
curl -X POST http://localhost:8000/api/v1/webhooks/asaas \
-H "Content-Type: application/json" \
-H "asaas-signature: $SIGNATURE" \
-d "$BODY"
Verificando o Processamento
Após enviar um webhook, verifique o estado do saque no Supabase:
-- No SQL Editor do Supabase
SELECT id, status, asaas_transfer_id, completed_at, error_message
FROM withdrawals
WHERE asaas_transfer_id = 'tra_000012345';
Eventos ASAAS e Ações
| Evento | Status Final | Ação |
|---|---|---|
TRANSFER_DONE |
completed |
Pix enviado com sucesso |
TRANSFER_APPROVED |
completed |
Transferência aprovada |
TRANSFER_FAILED |
failed |
Falha na transferência |
TRANSFER_CANCELLED |
failed |
Transferência cancelada |
| Outros eventos | — | Ignorados (retorna {"status": "ok"}) |
Troubleshooting
Erro 401: "Assinatura inválida"
- Verifique se
ASAAS_WEBHOOK_SECRETno.envé exatamente igual ao configurado no ASAAS - Confirme que o body não foi modificado antes da validação
- Use
hmac.compare_digest()(timing-safe) — nunca==
Webhook não chegando
- Verifique se a URL está acessível publicamente (use ngrok em dev)
- Confirme que o endpoint retorna
200 OK— ASAAS faz retry em caso de falha - Verifique os logs de entrega no dashboard ASAAS
Saque não atualizado após TRANSFER_DONE
- Confirme que
asaas_transfer_idno webhook bate com o registro emwithdrawals - Verifique se o
transfer_idfoi salvo corretamente após a chamada ASAAS - Consulte os logs do backend para erros de banco de dados
Retentativas ASAAS
O ASAAS faz retentativas em caso de falha: - Primeira tentativa: imediata - Segunda tentativa: 5 minutos depois - Terceiras tentativas: 30 minutos, 2 horas, 6 horas
O endpoint deve retornar 200 OK para sinalizar que o webhook foi processado.
Retornos de erro causam retentativas mesmo que o evento já tenha sido processado.
Por isso, o processamento é idempotente — processar o mesmo evento duas vezes não causa dano: