Tutorial focado em implementar o receptor de webhooks. Referência completa de eventos: guia de webhooks.
1. Endpoint no seu servidor
Exponha uma rota POST pública com HTTPS:
POST https://api.suaempresa.com/goatpay/webhook
Em produção a GoatPay exige HTTPS. Em desenvolvimento use ngrok, Cloudflare Tunnel ou similar.
O handler deve:
- Ler o raw body (antes de parsear JSON)
- Validar
x-goatpay-signature
- Processar ou enfileirar o evento
- Responder
200 rapidamente
2. Cadastrar na GoatPay
curl -X POST https://api.goatpay.com.br/v1/webhooks/create \
-H "X-API-Key: gp_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.suaempresa.com/goatpay/webhook",
"events": [
"payment.paid",
"payment.created",
"payment.pix.expired",
"transfer.completed",
"payment_link.paid"
]
}'
Guarde o whsec_... da resposta — só aparece na criação. Armazene em GOATPAY_WEBHOOK_SECRET.
Permissão: webhooks/create.
3. Validar assinatura (Node.js / Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
// Raw body obrigatório para HMAC
app.post(
"/goatpay/webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["x-goatpay-signature"] as string | undefined;
const secret = process.env.GOATPAY_WEBHOOK_SECRET!;
if (!verifySignature(req.body, signature, secret)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body.toString());
const deliveryId = event.id;
if (await alreadyProcessed(deliveryId)) {
return res.status(200).json({ ok: true });
}
switch (event.event) {
case "payment.paid":
await handlePaymentPaid(event.data);
break;
case "payment.pix.expired":
await handlePixExpired(event.data);
break;
// ...
}
await markProcessed(deliveryId);
return res.status(200).json({ ok: true });
},
);
function verifySignature(
rawBody: Buffer,
signatureHeader: string | undefined,
webhookSecret: string,
): boolean {
if (!signatureHeader?.startsWith("sha256=")) return false;
const received = signatureHeader.slice("sha256=".length);
const expected = crypto
.createHmac("sha256", webhookSecret)
.update(rawBody)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(received, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python (FastAPI)
import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/goatpay/webhook")
async def goatpay_webhook(request: Request):
raw = await request.body()
sig = request.headers.get("x-goatpay-signature", "")
secret = os.environ["GOATPAY_WEBHOOK_SECRET"]
if not verify(raw, sig, secret):
raise HTTPException(401, "Invalid signature")
event = json.loads(raw)
# idempotência + processamento...
return {"ok": True}
def verify(raw: bytes, header: str, secret: str) -> bool:
if not header.startswith("sha256="):
return False
received = header[7:]
expected = hmac.new(secret.encode(), raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, received)
4. Idempotência
A GoatPay pode reenviar a mesma entrega (retentativas: 1 min, 5 min, 15 min, 1 h, 4 h — até 5 tentativas).
Persista o id da entrega antes de efeitos colaterais:
if (await db.webhookDelivery.exists(deliveryId)) {
return res.status(200).json({ ok: true });
}
5. Eventos que você provavelmente precisa
| Evento | Quando usar |
|---|
payment.paid | PIX recebido — liberar pedido |
payment.created | Cobrança registrada (opcional) |
payment.pix.expired | QR expirou — cancelar checkout |
transfer.completed | Saque PIX concluído |
payment_link.paid | Link de pagamento pago |
refund.completed | Reembolso confirmado |
med.created | Disputa MED aberta |
Lista completa: guia de webhooks — eventos.
6. Escopo por API key
| Como o endpoint foi criado | Eventos recebidos |
|---|
POST /v1/webhooks/create | Só transações da mesma API key |
| Pelo app em Integrações → Webhooks | Toda a conta |
Se o webhook não chega, confira se a cobrança foi criada com a mesma chave que cadastrou o endpoint.
Checklist
Próximo passo
Troubleshooting de webhooks · Cadastrar endpoint