Webhooks
Six events, one envelope, at-least-once delivery with exponential backoff.
Register an endpoint in your product console and subscribe it to the events you want. Only public URLs are accepted — a localhost or private-range target is refused at delivery time, so use a tunnel while developing.
The envelope
{
"id": "0a4b…", // unique per delivery attempt group: your idempotency key
"event": "purchase.completed",
"created_at": "2026-09-20T22:14:03.114Z",
"data": { "purchase_id": "…", "user_id": "…", "product_id": "…", "gross_minor": 4990, "currency": "BRL" }
}- guiru-eventstring
- The event name, so you can route before parsing.
- guiru-signature`t=…,v1=…`
- HMAC-SHA256 over
timestamp.body. See verifying. - guiru-signature-previous`t=…,v1=…`
- Present only during a secret rotation window — signed with the old secret so a receiver that has not redeployed yet still verifies.
- user-agent`Guiru-Webhooks/1.0`
- Not an authentication mechanism. Never treat it as one.
Events
entitlement.created- Someone gained access: a purchase cleared, a subscription started, Member opened the product, or an operator granted it.
entitlement.canceled- Access was cancelled.
ends_atmay be in the future — a cancelled subscription usually runs to the end of its period. entitlement.expired- Access ran out on its own.
entitlement.refunded- Access was withdrawn because the money went back.
purchase.completed- A Buy with Guiru payment cleared. Carries
gross_minorandcurrency— the currency the buyer actually paid in. purchase.refunded- A purchase was refunded, or lost a chargeback (
chargeback: true). webhook.test- Sent when you press *Send test* in the console. Same signature, same envelope;
data.messagesays it is a test. Handle it by acknowledging with 2xx and doing nothing.
Delivery
Delivery is at-least-once. We wait up to ten seconds for a response; any 2xx is success, anything else is a failure and is retried with exponential backoff: 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours, then the delivery is marked dead and stops. Recent deliveries, their status codes and their errors are listed in the product console.
Idempotency
Key on id from the envelope and ignore an event you have already applied. Order is not guaranteed either — an entitlement.created can land after the entitlement.canceled that followed it — so make each handler state-setting rather than state-advancing, or re-read the entitlement and trust that instead.