Webhooks API
Webhooks permitem que você receba notificações HTTP em tempo real quando eventos importantes acontecem no Tarefa AI. Ao invés de fazer polling, o sistema envia automaticamente dados para sua URL configurada.
Visão Geral
Como Funcionam
- Você configura uma URL de webhook no dashboard
- Quando um evento ocorre (tarefa completa, falha, etc), enviamos um POST para sua URL
- Sua aplicação recebe o payload e processa conforme necessário
- Você retorna HTTP 200 para confirmar recebimento
Eventos Suportados
task.created- Nova tarefa criadatask.updated- Tarefa atualizadatask.deleted- Tarefa removidatask.started- Execução de tarefa iniciadatask.completed- Tarefa completada com sucessotask.failed- Tarefa falhouexecution.started- Execução iniciadaexecution.completed- Execução completadaexecution.failed- Execução falhou
Estrutura da URL
https://api.tarefaai.com/api/webhooks/{userId}/{secret}
Parâmetros
userId: ID do usuário (numérico)secret: Secret key único para validação
Exemplo:
https://api.tarefaai.com/api/webhooks/123/wh_abc123def456
Configurando um Webhook
1. Criar Webhook via Dashboard
Acesse Configurações > Webhooks > Novo Webhook
Campos necessários:
- URL: Sua URL de destino (deve ser HTTPS em produção)
- Eventos: Selecione quais eventos deseja receber
- Secret: Gerado automaticamente (use para validar assinaturas)
2. Criar Webhook via API
curl -X POST https://api.tarefaai.com/api/webhooks \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"url": "https://meuapp.com/webhook",
"events": ["task.completed", "task.failed"],
"description": "Webhook para notificações de tasks"
}'Response (201 Created):
{
"success": true,
"webhook": {
"id": "wh_123",
"userId": 123,
"url": "https://meuapp.com/webhook",
"secret": "whsec_abc123def456...",
"events": ["task.completed", "task.failed"],
"isActive": true,
"createdAt": "2025-01-17T10:00:00.000Z"
}
}IMPORTANTE: Guarde o secret com segurança. Ele é mostrado apenas uma vez.
Recebendo Webhooks
Headers da Requisição
Todos os webhooks incluem headers especiais:
Content-Type: application/json
User-Agent: TarefaAI-Webhook/1.0
X-Webhook-ID: wh_123
X-Webhook-Signature: sha256=abc123...
X-Request-ID: req_456
X-Event-Type: task.completed
X-Delivery-Attempt: 1Payload Base
Todos os payloads seguem esta estrutura:
interface WebhookPayload {
event: string;
timestamp: string; // ISO 8601
webhookId: string;
userId: number;
data: EventData; // Varia por tipo de evento
}Eventos Detalhados
task.created
Disparado quando uma nova tarefa é criada.
Payload:
{
"event": "task.created",
"timestamp": "2025-01-17T10:00:00.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias",
"prompt": "Resuma as principais notícias...",
"recurrence": "daily",
"scheduledFor": "2025-01-18T09:00:00.000Z",
"status": "active",
"createdAt": "2025-01-17T10:00:00.000Z"
}
}
}task.completed
Disparado quando uma tarefa é executada com sucesso.
Payload:
{
"event": "task.completed",
"timestamp": "2025-01-18T09:05:23.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias",
"status": "active"
},
"execution": {
"id": 789,
"status": "completed",
"result": "Resumo das notícias:\n\n1. Nova versão do React...",
"tokensUsed": {
"input": 150,
"output": 450,
"total": 600
},
"processingTimeMs": 2340,
"startedAt": "2025-01-18T09:00:00.000Z",
"completedAt": "2025-01-18T09:05:23.000Z"
}
}
}task.failed
Disparado quando uma tarefa falha na execução.
Payload:
{
"event": "task.failed",
"timestamp": "2025-01-18T09:02:15.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias",
"status": "active"
},
"execution": {
"id": 790,
"status": "failed",
"error": "API rate limit exceeded",
"errorCode": "RATE_LIMIT_EXCEEDED",
"startedAt": "2025-01-18T09:00:00.000Z",
"failedAt": "2025-01-18T09:02:15.000Z",
"processingTimeMs": 135000
},
"retryScheduled": true,
"nextRetryAt": "2025-01-18T09:12:15.000Z"
}
}execution.started
Disparado quando uma execução inicia (antes de chamar a IA).
Payload:
{
"event": "execution.started",
"timestamp": "2025-01-18T09:00:00.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias"
},
"execution": {
"id": 789,
"status": "running",
"startedAt": "2025-01-18T09:00:00.000Z",
"estimatedDurationMs": 3000
}
}
}task.updated
Disparado quando uma tarefa é editada.
Payload:
{
"event": "task.updated",
"timestamp": "2025-01-17T14:30:00.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias Tech",
"updatedAt": "2025-01-17T14:30:00.000Z"
},
"changes": {
"name": {
"from": "Resumo Diário de Notícias",
"to": "Resumo Diário de Notícias Tech"
},
"prompt": {
"from": "Resuma as principais notícias...",
"to": "Resuma as principais notícias de tecnologia..."
}
}
}
}task.deleted
Disparado quando uma tarefa é removida.
Payload:
{
"event": "task.deleted",
"timestamp": "2025-01-17T15:00:00.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"task": {
"id": 456,
"name": "Resumo Diário de Notícias",
"deletedAt": "2025-01-17T15:00:00.000Z"
}
}
}Segurança: Verificando Assinaturas
Todos os webhooks incluem uma assinatura HMAC-SHA256 no header X-Webhook-Signature.
Algoritmo
signature = HMAC-SHA256(webhook_secret, payload_body)
Implementação Node.js
import crypto from 'crypto';
function verifyWebhookSignature(
payload: string,
signature: string,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timing-safe comparison
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expectedSignature)
);
}
// Uso em Express.js
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'] as string;
const payload = req.body.toString('utf8');
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(payload);
// Processar evento...
res.status(200).json({ received: true });
});Implementação Python
import hmac
import hashlib
import json
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
signature = signature.replace('sha256=', '')
return hmac.compare_digest(signature, expected_signature)
# Uso em Flask
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return jsonify({'error': 'Invalid signature'}), 401
event = json.loads(payload)
# Processar evento...
return jsonify({'received': True})Implementação Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strings"
)
func verifyWebhookSignature(payload, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
signature = strings.TrimPrefix(signature, "sha256=")
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}Respondendo a Webhooks
Resposta Esperada
Sua aplicação deve retornar HTTP 200-299 rapidamente (idealmente < 5 segundos).
Response recomendado:
{
"received": true,
"processedAt": "2025-01-18T09:05:25.000Z"
}Processamento Assíncrono
Para tarefas longas, responda imediatamente e processe em background:
// ✅ BOM: Responde rápido e processa depois
app.post('/webhook', async (req, res) => {
// Valida assinatura
if (!verifySignature(req)) {
return res.status(401).send('Invalid signature');
}
// Responde imediatamente
res.status(200).json({ received: true });
// Processa em background
processWebhookAsync(req.body).catch(console.error);
});
// ❌ RUIM: Processa de forma síncrona
app.post('/webhook', async (req, res) => {
await longRunningProcess(req.body); // Pode timeout!
res.status(200).json({ received: true });
});Testando Webhooks
Health Check
Use o método GET para verificar se o webhook está ativo:
curl -X GET https://api.tarefaai.com/api/webhooks/123/wh_abc123Response:
{
"message": "Webhook endpoint is active",
"userId": "123",
"webhookId": "wh_123",
"acceptedEvents": [
"task.started",
"task.completed",
"task.failed"
],
"isActive": true
}Enviar Webhook de Teste
Via dashboard ou API:
curl -X POST https://api.tarefaai.com/api/webhooks/wh_123/test \
-H "Authorization: Bearer {token}"Payload de teste enviado:
{
"event": "test",
"timestamp": "2025-01-17T10:00:00.000Z",
"webhookId": "wh_123",
"userId": 123,
"data": {
"message": "This is a test webhook",
"testId": "test_456"
}
}Ferramentas de Teste
webhook.site: Receba webhooks em URL temporária para debug
# 1. Gere URL em https://webhook.site
# 2. Configure a URL no Tarefa AI
# 3. Dispare um evento de teste
# 4. Visualize o payload recebidongrok: Exponha localhost para testes locais
ngrok http 3000
# Use a URL gerada (https://abc123.ngrok.io) como webhook URLRetry e Timeouts
Política de Retry
Se sua URL não responder com 200-299, tentamos novamente:
| Tentativa | Espera |
|---|---|
| 1 | Imediato |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 15 minutos |
| 5 | 1 hora |
Após 5 falhas: Webhook marcado como "failed" e você recebe notificação.
Timeout
- Conexão: 5 segundos
- Resposta: 30 segundos
Se sua aplicação demorar mais de 30s para responder, consideramos timeout.
Headers de Retry
X-Delivery-Attempt: 3
X-Previous-Attempt: 2025-01-18T09:05:00.000ZMonitoramento
Dashboard de Webhooks
Acesse Configurações > Webhooks para ver:
- Status de cada webhook (ativo/inativo)
- Últimas 100 entregas
- Taxa de sucesso
- Tempo médio de resposta
- Logs de erros
Logs de Entrega
Cada entrega é registrada com:
{
"id": "delivery_789",
"webhookId": "wh_123",
"event": "task.completed",
"url": "https://meuapp.com/webhook",
"statusCode": 200,
"responseTime": 234,
"attempt": 1,
"success": true,
"timestamp": "2025-01-18T09:05:23.000Z"
}API de Logs
curl -X GET "https://api.tarefaai.com/api/webhooks/wh_123/logs?limit=50" \
-H "Authorization: Bearer {token}"Response:
{
"success": true,
"logs": [
{
"id": "delivery_789",
"event": "task.completed",
"statusCode": 200,
"responseTime": 234,
"success": true,
"timestamp": "2025-01-18T09:05:23.000Z"
}
],
"pagination": {
"total": 1250,
"limit": 50,
"offset": 0
}
}Rate Limits
- Máximo de webhooks por usuário: 10
- Máximo de eventos por webhook: Ilimitado
- Rate limit de entregas: 100/minuto por webhook
Troubleshooting
Webhook não está recebendo eventos
Checklist:
- ✅ Webhook está ativo no dashboard?
- ✅ URL está correta e acessível?
- ✅ Eventos corretos estão selecionados?
- ✅ Sua aplicação responde com 200?
- ✅ Firewall/CORS configurado corretamente?
Erros comuns
401 Invalid signature
{
"error": "Invalid signature"
}Causa: Secret key incorreto ou payload modificado.
Solução: Verifique se está usando o secret correto e validando a assinatura.
404 Webhook not found
{
"error": "Webhook not found or inactive"
}Causa: Webhook deletado ou desativado.
Solução: Verifique o status no dashboard.
429 Rate limit exceeded
{
"error": "Rate limit exceeded",
"message": "Maximum 100 requests per minute"
}Causa: Muitas entregas em curto período.
Solução: Implemente backoff exponencial ou aumente intervalo.
500 Server error
{
"error": "Internal server error",
"message": "Failed to process webhook"
}Causa: Erro no servidor Tarefa AI.
Solução: Verificamos automaticamente. Contate suporte se persistir.
Exemplos de Implementação
Express.js Completo
import express from 'express';
import crypto from 'crypto';
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
app.post(
'/webhook',
express.raw({ type: 'application/json' }),
async (req, res) => {
try {
// 1. Verificar assinatura
const signature = req.headers['x-webhook-signature'] as string;
const payload = req.body.toString('utf8');
if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Parse evento
const event = JSON.parse(payload);
console.log('Received event:', event.event);
// 3. Responder rapidamente
res.status(200).json({ received: true });
// 4. Processar em background
processEvent(event).catch(console.error);
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Internal error' });
}
}
);
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature.replace('sha256=', '')),
Buffer.from(expected)
);
}
async function processEvent(event: any) {
switch (event.event) {
case 'task.completed':
await handleTaskCompleted(event.data);
break;
case 'task.failed':
await handleTaskFailed(event.data);
break;
}
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});Próximos Passos
Referência: Implementação completa em /src/app/api/webhooks/[userId]/[secret]/route.ts