Docs Integração de Webhooks

Integração de Webhooks

Receba notificações em tempo real quando eventos acontecem na sua conta stryhub. Cada webhook é assinado com HMAC-SHA256, entregue com tentativas automáticas e registrado para total visibilidade.

Visão Geral

A stryhub envia requisições HTTP POST para as URLs de endpoint configuradas sempre que eventos significativos ocorrem — como pagamentos concluídos, alterações de assinatura ou novos cadastros de clientes.

Diferente dos webhooks brutos do Stripe, a stryhub entrega payloads simplificados e consistentes que são fáceis de interpretar e processar. Você não precisa entender a estrutura de eventos do Stripe — apenas trate os eventos da stryhub.

Múltiplos endpoints: Você pode configurar quantos endpoints de webhook precisar. Cada endpoint pode se inscrever em diferentes tipos de evento, facilitando o roteamento de eventos para diferentes serviços.

Configuração

Para configurar um endpoint de webhook:

  1. Acesse Webhooks no seu painel administrativo
  2. Clique em New Integration
  3. Insira a URL onde deseja receber os eventos (deve ser HTTPS em produção)
  4. Adicione uma descrição opcional (ex.: "Servidor de Produção" ou "Ambiente de Staging")
  5. Selecione quais eventos deseja receber (todos são selecionados por padrão)
  6. Clique em Create Integration

Após a criação, você receberá uma chave de assinatura que começa com whsec_. Armazene-a com segurança — você precisará dela para verificar as assinaturas dos webhooks.

Formato do Payload

Cada entrega de webhook contém um payload JSON com esta estrutura:

payload.json
{
  "id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "type": "payment.completed",
  "created_at": "2026-02-16T14:30:00.000Z",
  "data": {
    "customer": {
      "email": "john@example.com",
      "name": "John Doe",
      "stripe_customer_id": "cus_abc123"
    },
    "amount": 49.99,
    "net_amount": 48.49,
    "platform_fee": 1.50,
    "currency": "usd",
    "subscription_id": "sub_xyz789",
    "mode": "subscription"
  }
}
Campo Tipo Descrição
id string Identificador único do evento (UUID v4)
type string Tipo do evento (ex.: payment.completed)
created_at string Timestamp ISO 8601
data object Dados específicos do evento (varia conforme o tipo de evento)

Headers HTTP

Cada requisição de webhook inclui estes headers:

Header Exemplo Descrição
Content-Type application/json Sempre JSON
X-Stryhub-Signature t=1708100000,v1=abc123... Assinatura HMAC-SHA256 para verificação
X-Stryhub-Event payment.completed Tipo do evento
X-Stryhub-Delivery del_uuid ID único de entrega (para idempotência)
User-Agent Stryhub-Webhooks/1.0 String do user agent

Verificação de Assinatura

Cada entrega de webhook é assinada com a chave de assinatura única do seu endpoint usando HMAC-SHA256. Você deve sempre verificar a assinatura antes de processar o evento.

Como funciona

  1. Extraia o timestamp (t) e a assinatura (v1) do header X-Stryhub-Signature
  2. Construa o conteúdo assinado: {timestamp}.{raw_body}
  3. Compute o HMAC-SHA256 usando sua chave de assinatura
  4. Compare a assinatura computada com o valor v1
  5. Opcionalmente, verifique se o timestamp é recente (dentro de 5 minutos) para prevenir ataques de replay

Exemplos de Código

const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  // Analisa o header de assinatura
  const parts = {};
  signatureHeader.split(',').forEach(part => {
    const [key, value] = part.split('=');
    parts[key] = value;
  });

  const timestamp = parts['t'];
  const signature = parts['v1'];

  // Constrói o conteúdo assinado
  const signedContent = `${timestamp}.${rawBody}`;

  // Computa a assinatura esperada
  const expected = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');

  // Compara as assinaturas (timing-safe)
  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );

  // Verifica a validade do timestamp (tolerância de 5 min)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) {
    throw new Error('Webhook timestamp too old');
  }

  return isValid;
}

// Exemplo com Express.js
app.post('/webhooks/stryhub', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-stryhub-signature'];
  const secret = process.env.STRYHUB_WEBHOOK_SECRET;

  try {
    const isValid = verifyWebhook(req.body.toString(), signature, secret);
    if (!isValid) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    const event = JSON.parse(req.body);
    console.log('Received event:', event.type, event.id);

    // Trata o evento
    switch (event.type) {
      case 'payment.completed':
        // Ativar assinatura, enviar e-mail de boas-vindas, etc.
        break;
      case 'subscription.canceled':
        // Revogar acesso, enviar e-mail de retenção, etc.
        break;
      // ... tratar outros eventos
    }

    res.status(200).json({ received: true });
  } catch (err) {
    console.error('Webhook error:', err.message);
    res.status(400).json({ error: err.message });
  }
});
import hmac
import hashlib
import time
import json
from flask import Flask, request, jsonify

app = Flask(__name__)

def verify_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    """Verifica a assinatura do webhook da stryhub."""
    # Analisa o header de assinatura
    parts = {}
    for part in signature_header.split(','):
        key, value = part.split('=', 1)
        parts[key] = value

    timestamp = parts.get('t', '')
    signature = parts.get('v1', '')

    # Constrói o conteúdo assinado
    signed_content = f"{timestamp}.{raw_body.decode('utf-8')}"

    # Computa a assinatura esperada
    expected = hmac.new(
        secret.encode('utf-8'),
        signed_content.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    # Compara as assinaturas (timing-safe)
    is_valid = hmac.compare_digest(signature, expected)

    # Verifica a validade do timestamp (tolerância de 5 min)
    age = int(time.time()) - int(timestamp)
    if age > 300:
        raise ValueError('Webhook timestamp too old')

    return is_valid


@app.route('/webhooks/stryhub', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Stryhub-Signature', '')
    secret = os.environ['STRYHUB_WEBHOOK_SECRET']

    try:
        is_valid = verify_webhook(request.data, signature, secret)
        if not is_valid:
            return jsonify({'error': 'Invalid signature'}), 401

        event = json.loads(request.data)
        print(f"Received event: {event['type']} {event['id']}")

        # Trata o evento
        if event['type'] == 'payment.completed':
            # Ativar assinatura, enviar e-mail de boas-vindas, etc.
            pass
        elif event['type'] == 'subscription.canceled':
            # Revogar acesso, enviar e-mail de retenção, etc.
            pass

        return jsonify({'received': True}), 200

    except Exception as e:
        print(f"Webhook error: {e}")
        return jsonify({'error': str(e)}), 400
<?php
// Handler de webhook em PHP

function verifyWebhook(string $rawBody, string $signatureHeader, string $secret): bool
{
    // Analisa o header de assinatura
    $parts = [];
    foreach (explode(',', $signatureHeader) as $part) {
        [$key, $value] = explode('=', $part, 2);
        $parts[$key] = $value;
    }

    $timestamp = $parts['t'] ?? '';
    $signature = $parts['v1'] ?? '';

    // Constrói o conteúdo assinado
    $signedContent = "{$timestamp}.{$rawBody}";

    // Computa a assinatura esperada
    $expected = hash_hmac('sha256', $signedContent, $secret);

    // Compara as assinaturas (timing-safe)
    $isValid = hash_equals($signature, $expected);

    // Verifica a validade do timestamp (tolerância de 5 min)
    $age = time() - intval($timestamp);
    if ($age > 300) {
        throw new Exception('Webhook timestamp too old');
    }

    return $isValid;
}

// Trata o webhook
$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_STRYHUB_SIGNATURE'] ?? '';
$secret = getenv('STRYHUB_WEBHOOK_SECRET');

try {
    $isValid = verifyWebhook($rawBody, $signature, $secret);

    if (!$isValid) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid signature']);
        exit;
    }

    $event = json_decode($rawBody, true);
    error_log("Received event: {$event['type']} {$event['id']}");

    // Trata o evento
    switch ($event['type']) {
        case 'payment.completed':
            // Ativar assinatura, enviar e-mail de boas-vindas, etc.
            break;
        case 'subscription.canceled':
            // Revogar acesso, enviar e-mail de retenção, etc.
            break;
    }

    http_response_code(200);
    echo json_encode(['received' => true]);

} catch (Exception $e) {
    error_log("Webhook error: " . $e->getMessage());
    http_response_code(400);
    echo json_encode(['error' => $e->getMessage()]);
}
?>

Eventos Suportados

A stryhub traduz eventos complexos do Stripe em notificações simples e acionáveis:

Evento Descrição Quando é disparado
checkout.completed Sessão de checkout concluída O cliente finaliza um checkout (avulso ou assinatura)
payment.completed Pagamento realizado com sucesso Cobrança bem-sucedida (inicial ou recorrente)
payment.failed Pagamento falhou Cartão recusado ou erro no pagamento
subscription.created Nova assinatura O cliente assina um produto recorrente
subscription.renewed Assinatura renovada Pagamento recorrente realizado com sucesso
subscription.canceled Assinatura cancelada A assinatura é totalmente cancelada
subscription.paused Assinatura pausada A cobrança da assinatura é pausada
subscription.resumed Assinatura retomada A assinatura pausada é reativada
subscription.past_due Pagamento em atraso Pagamento recorrente falhou; assinatura em risco
customer.created Novo cliente Cliente registrado pela primeira vez

Política de Tentativas

Se o seu endpoint retornar um código de status diferente de 2xx (ou expirar após 15 segundos), a stryhub automaticamente retenta a entrega:

Tentativa Intervalo Acumulado
Imediata 0
1 minuto 1 minuto
5 minutos 6 minutos
30 minutos 36 minutos
2 horas ~2,5 horas
6ª (final) 24 horas ~26,5 horas

Após 6 tentativas sem sucesso, a entrega é marcada como falha. Você pode retentar manualmente entregas com falha a partir do painel a qualquer momento.

Desativação automática: Se um endpoint acumular 50 falhas consecutivas em todas as entregas, ele é automaticamente desativado para evitar carga desnecessária. Você pode reativá-lo a partir do painel após corrigir o problema.

Boas Práticas

1. Responda rapidamente

Retorne uma resposta 2xx o mais rápido possível (dentro de 15 segundos). Se precisar fazer processamento pesado, confirme o recebimento do webhook imediatamente e processe-o de forma assíncrona (ex.: usando uma fila de tarefas).

2. Verifique as assinaturas

Sempre verifique o header X-Stryhub-Signature antes de confiar no payload. Isso impede que atacantes enviem eventos falsos para o seu endpoint.

3. Trate duplicatas (idempotência)

Devido às tentativas automáticas, seu endpoint pode receber o mesmo evento mais de uma vez. Use o campo id no payload para detectar e ignorar eventos duplicados.

4. Use o ID de entrega

O header X-Stryhub-Delivery contém um ID único de entrega. Registre-o para depuração e use-o para rastrear tentativas individuais de entrega.

5. Monitore seus logs

Verifique regularmente os logs de entrega de webhooks no seu painel. Entregas com falha podem indicar problemas com seu endpoint, rede ou lógica da aplicação.

6. Use HTTPS

Sempre use HTTPS para a URL do seu endpoint em produção. Isso garante que o payload e a assinatura sejam criptografados em trânsito.