API
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
| Evento | Quando ocorre |
|---|---|
payment.status_changed | Sempre que o status muda |
payment.approved | Pagamento confirmado pelo gateway |
payment.recused | Pagamento negado (saldo, fraude, etc.) |
payment.error | Erro 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.