Webhooks
Receba eventos de pagamento assinados, valide a assinatura e reaja a eles.
Webhooks são como a Gravity Pay avisa o seu sistema quando algo acontece — um pagamento confirmado, uma assinatura renovada, um estorno. Eles são a fonte da verdade: chegam mesmo que o cliente feche o navegador.
Duas formas de receber
Endpoints do painel
Cadastre URLs fixas em Integrações → Webhooks e escolha os eventos (e, se quiser, os produtos). Recebem todos os eventos que casarem.
postbackUrl da cobrança
Ao criar uma cobrança via API, informe um postbackUrl. Ele recebe os eventos daquela
transação específica — somado aos endpoints do painel.
Eventos
| Evento | Quando dispara |
|---|---|
payment.paid | Um pagamento foi confirmado. |
payment.failed | Um pagamento foi recusado. |
payment.refunded | Um pagamento foi estornado. |
subscription.created | Uma assinatura ficou ativa. |
subscription.renewed | Uma assinatura foi renovada (novo ciclo pago). |
subscription.canceled | Uma assinatura foi cancelada. |
Formato do payload
Toda entrega é um POST com corpo JSON neste formato:
{
"id": "whd_9f2c...",
"event": "payment.paid",
"createdAt": "2026-06-10T01:19:06.544Z",
"data": {
"id": "pay_...",
"provider": "STRIPE",
"status": "PAID",
"amountInCents": 12900,
"currency": "usd",
"chargeId": "chg_2e59538f...",
"customerEmail": "client@acme.com",
"metadata": { "invoiceId": "1234" }
}
}Para eventos de uma cobrança via API, o data.metadata traz a metadata que você enviou na
criação — use o invoiceId (ou o que for) para dar baixa.
E os headers:
| Header | Descrição |
|---|---|
X-GravityPay-Event | O tipo do evento (ex.: payment.paid). |
X-GravityPay-Delivery | Id único da entrega (= id do payload). Idempotência. |
X-GravityPay-Signature | Assinatura HMAC (veja abaixo). |
Validando a assinatura
Cada entrega é assinada com HMAC-SHA256. O header tem o formato t=<unix>,v1=<hmac>, e o HMAC
é calculado sobre `${t}.${body}` usando o secret.
Qual secret usar?
- Endpoints do painel: o signing secret exibido ao criar o endpoint.
- postbackUrl: o
postbackSecretretornado na resposta doPOST /v1/chargesdaquela cobrança. Cada cobrança tem o seu.
import crypto from "node:crypto";
function verify(secret, header, rawBody) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
// Express — use o corpo CRU (raw), não o JSON já parseado.
app.post("/webhooks/gravitypay", express.raw({ type: "*/*" }), (req, res) => {
const ok = verify(
process.env.POSTBACK_SECRET,
req.headers["x-gravitypay-signature"],
req.body.toString("utf8"),
);
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body.toString("utf8"));
if (event.event === "payment.paid") {
// dê baixa na fatura event.data.metadata.invoiceId
}
res.status(200).end();
});import hmac, hashlib
def verify(secret: str, header: str, raw_body: str) -> bool:
parts = dict(p.split("=") for p in header.split(","))
expected = hmac.new(
secret.encode(), f"{parts['t']}.{raw_body}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(parts["v1"], expected)Use o corpo cru
Valide a assinatura sobre o corpo exatamente como recebido (raw). Se você re-serializar o JSON, a assinatura não vai bater.
postbackUrl
Quando você cria uma cobrança com postbackUrl, todos os eventos daquela transação
(payment.paid, payment.failed, payment.refunded) são entregues também nessa URL —
além dos endpoints do painel. É ideal para reconciliar uma fatura sem precisar manter um endpoint
fixo.
O postbackSecret que valida essas entregas vem uma única vez, na resposta da criação da
cobrança. Guarde-o junto com o id da cobrança/fatura.
Idempotência
A mesma entrega pode chegar mais de uma vez (ex.: ao reprocessar). Use o
X-GravityPay-Delivery (= id do payload) como chave de idempotência e ignore duplicatas.
Resposta e re-tentativas
Responda 2xx rapidamente (idealmente em até alguns segundos) para confirmar o recebimento. Qualquer outro status é tratado como falha. Entregas que falham ficam registradas no painel (Integrações → Webhooks → Entregas), de onde você pode reprocessar manualmente.

