Skip to content

Webhooks, verify, store, reconcile

Orca sends a webhook every time an entitlement changes: new purchase, renewal, cancellation, refund. The payload is the customer’s updated entitlement. The request carries two headers, x-orca-signature and x-orca-timestamp, and the server SDKs verify both.

app.post('/webhooks/orca', async (req, res) => {
try {
const event = await orca.constructWebhookEvent({
webhookPublicKey: process.env.ORCA_WEBHOOK_PUBLIC_KEY!,
rawPayload: req.rawBody, // the raw bytes, not parsed JSON
signatureHeader: req.headers['x-orca-signature'] as string,
timestampHeader: req.headers['x-orca-timestamp'] as string,
})
// persist event (the customer's updated entitlement) here
res.sendStatus(200)
} catch {
res.sendStatus(400) // bad signature or timestamp older than 5 minutes
}
})

Two things bite in practice:

  1. Raw body. Verify against the exact bytes received. Most frameworks parse JSON before your handler runs; capture the raw body first.
  2. Clock skew. Verification rejects timestamps more than five minutes old. Keep your server clock synced.

As with any webhook system, deliveries can be repeated or delayed. Design accordingly:

  • Idempotent writes. Upsert the entitlement state by (customer email, entitlement id). Use documented update-version or timestamp metadata to reject stale updates, not the expiry date: refunds and revocations can shorten it. If ordering metadata is unavailable, reconcile with getActiveEntitlements(email, env) before updating cached access.
  • Acknowledge after persisting. Return 200 only once the change is durable. A 5xx should mean “please send this again”.
  • Reconcile. Periodically call getActiveEntitlements(email, env) for customers with recent activity and compare with your stored state. Alert when they disagree.
  • Alert on silence. If you normally receive renewals every day and have received none for a day, something is wrong upstream or with your endpoint.

The documented payload is the updated entitlement, not a typed event name. Derive the transition yourself by comparing with the previous state you stored (active → inactive is an expiry or revocation; expiry date moved forward is a renewal). The lifecycle guide lists the transitions and the store signals behind them.

Make a sandbox purchase, then cancel and refund it in the store’s console. You should receive a webhook for each change. If you receive the purchase but not the cancellation, the store’s server notifications to Orca are probably not configured, see the store integration pages for the notification URL setup.