Webhooks

api
backend
webhooks
integração

Guia completo para configurar e usar webhooks para receber notificações em tempo real de eventos

Nesta Página

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

  1. Você configura uma URL de webhook no dashboard
  2. Quando um evento ocorre (tarefa completa, falha, etc), enviamos um POST para sua URL
  3. Sua aplicação recebe o payload e processa conforme necessário
  4. Você retorna HTTP 200 para confirmar recebimento

Eventos Suportados

  • task.created - Nova tarefa criada
  • task.updated - Tarefa atualizada
  • task.deleted - Tarefa removida
  • task.started - Execução de tarefa iniciada
  • task.completed - Tarefa completada com sucesso
  • task.failed - Tarefa falhou
  • execution.started - Execução iniciada
  • execution.completed - Execução completada
  • execution.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: 1

Payload 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_abc123

Response:

{
  "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 recebido

ngrok: Exponha localhost para testes locais

ngrok http 3000
# Use a URL gerada (https://abc123.ngrok.io) como webhook URL

Retry e Timeouts

Política de Retry

Se sua URL não responder com 200-299, tentamos novamente:

TentativaEspera
1Imediato
21 minuto
35 minutos
415 minutos
51 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.000Z

Monitoramento

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:

  1. ✅ Webhook está ativo no dashboard?
  2. ✅ URL está correta e acessível?
  3. ✅ Eventos corretos estão selecionados?
  4. ✅ Sua aplicação responde com 200?
  5. ✅ 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