Webhooks: order.paid and order.refunded

Appside delivers every paid and refunded order to your Fulfillment URL as a signed HTTPS POST. This is the only place you should grant or revoke items.

Payload

{
  "type": "order.paid",
  "event_id": "TEST-6f1c…",
  "mode": "test",
  "tenant": "orbit",
  "order_id": "TEST-6f1c…",
  "player_id": "player-123",
  "probe": false,
  "order": { "id": "TEST-6f1c…", "sku": "voyager", "credits": 1400, "price": 799, "currency": "USD", "version": 3 }
}

Headers

HeaderMeaning
x-appside-timestampUnix seconds when signed
x-appside-signaturehex HMAC-SHA256 of "<timestamp>." + rawBody with your webhook secret
idempotency-keythe event_id; dedupe on it

Verify the signature

Reject if the timestamp is more than 5 minutes off, then compare in constant time.

import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(raw, h, secret) {
  const ts = h['x-appside-timestamp'], sig = h['x-appside-signature'] || '';
  if (!/^\d{10}$/.test(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const want = createHmac('sha256', secret).update(`${ts}.`).update(raw).digest();
  const got = Buffer.from(sig, 'hex');
  return got.length === want.length && timingSafeEqual(got, want);
}

Delivery guarantees