Webhooks

5 min de leitura

O Orquestraio notifica seu backend de forma assíncrona quando o status de um pagamento muda — essencial para PIX e Boleto.

Normalizacao automatica: Independente do gateway (Stripe, MercadoPago, PayPal), todos os webhooks chegam no mesmo formato. Você implementa uma vez — o Orquestraio traduz o resto.

Payload Normalizado

// POST para a URL configurada no seu tenant // Header: X-Orquestraio-Signature: sha256=a1b2c3d4... { "event": "payment.status_changed", "paymentId": "550e8400-e29b-41d4-a716-446655440000", "status": "APPROVED", "amount": 197.00, "currency": "BRL", "provider": "MERCADO_PAGO", "timestamp": "2026-02-21T15:31:45Z" }

Verificação de Assinatura (HMAC-SHA256)

Verificação obrigatória em produção. Sem verificar a assinatura, qualquer pessoa que conheça a URL do seu endpoint pode enviar eventos falsos e acionar lógica crítica de negócio (liberar pedidos, creditar saldo, etc.). Nunca processe um webhook sem validar o header X-Orquestraio-Signature.

Cada notificação é assinada com HMAC-SHA256 usando um secret exclusivo por tenant. O header enviado tem o formato:

X-Orquestraio-Signature: sha256=a1b2c3d4e5f6789abc...

Para validar: compute HMAC-SHA256(rawBody, webhookSecret) e compare com o valor do header usando comparação em tempo constante (evita timing attacks).

const crypto = require('crypto'); function verifySignature(rawBody, signatureHeader, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody) // rawBody = Buffer, antes do JSON.parse .digest('hex'); // timingSafeEqual previne timing attacks return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader)); } // Express: use express.raw() ANTES do json() parser app.post('/webhooks/orquestraio', express.raw({type: '*/*'}), (req, res) => { const sig = req.headers['x-orquestraio-signature']; if (!sig || !verifySignature(req.body, sig, process.env.ORQUESTRAIO_WEBHOOK_SECRET)) { return res.status(401).end(); } const event = JSON.parse(req.body.toString()); if (event.event === 'payment.approved') fulfillOrder(event.paymentId); res.status(200).json({ received: true }); });
import hmac, hashlib, os from flask import request, jsonify, abort def verify_signature(raw_body: bytes, sig_header: str, secret: str) -> bool: expected = 'sha256=' + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() # compare_digest previne timing attacks return hmac.compare_digest(expected, sig_header) @app.route('/webhooks/orquestraio', methods=['POST']) def webhook(): sig = request.headers.get('X-Orquestraio-Signature', '') if not verify_signature( request.get_data(), sig, os.environ['ORQUESTRAIO_WEBHOOK_SECRET'] ): abort(401) event = request.json if event['event'] == 'payment.approved': fulfill_order(event['paymentId']) return jsonify({'received': True}), 200
import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/hex" "fmt" "io" "net/http" ) func verifySignature(body []byte, sigHeader, secret string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(body) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) // ConstantTimeCompare previne timing attacks return subtle.ConstantTimeCompare([]byte(expected), []byte(sigHeader)) == 1 } func webhookHandler(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) sig := r.Header.Get("X-Orquestraio-Signature") if !verifySignature(body, sig, os.Getenv("ORQUESTRAIO_WEBHOOK_SECRET")) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // processar evento... w.WriteHeader(http.StatusOK) }
Use o body bruto, antes do parse. Parsear o JSON antes de calcular o HMAC (ex: re-serializar com JSON.stringify) pode alterar a ordem das chaves e invalidar a assinatura. Sempre compute o HMAC sobre o body como bytes recebidos pelo servidor.

Implementação Básica

// Responda 200 em ate 5s ou o Orquestraio retentara app.post('/webhooks/orquestraio', (req, res) => { const { event, paymentId, status } = req.body; if (event === 'payment.status_changed' && status === 'APPROVED') { fulfillOrder(paymentId); } res.status(200).json({ received: true }); });
@app.route('/webhooks/orquestraio', methods=['POST']) def webhook(): data = request.json if data['event'] == 'payment.status_changed': if data['status'] == 'APPROVED': fulfill_order(data['paymentId']) return jsonify({'received': True}), 200
func webhookHandler(w http.ResponseWriter, r *http.Request) { var evt WebhookEvent json.NewDecoder(r.Body).Decode(&evt) if evt.Status == "APPROVED" { fulfillOrder(evt.PaymentID) } w.WriteHeader(http.StatusOK) }

Eventos Disponíveis

EventoQuando ocorre
payment.status_changedSempre que o status muda
payment.approvedPagamento confirmado pelo gateway
payment.recusedPagamento negado (saldo, fraude, etc.)
payment.errorErro técnico no gateway
Timeout e retry: Seu endpoint deve responder 200 OK em até 5 segundos. Se falhar, o Orquestraio tentará novamente com backoff exponencial (1min, 5min, 15min, 1h). Implemente idempotência no seu handler usando paymentId como chave.