Skip to content

Webhooks

Subscribe a URL and 1UP will POST to it whenever something happens on your account, rather than you polling for it.

Event Fires when
order.created An order is placed — through the API, the web app, or Zapier.
Terminal window
curl -X POST https://api.1up.gift/webhooks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "order.created",
"targetUrl": "https://hooks.example.com/1up"
}'
Response
{
"id": "6f2a1c84-9b3d-4e77-a1f0-23c9e5d8b410",
"event": "order.created",
"targetUrl": "https://hooks.example.com/1up",
"secret": "whsec_7Yk2pQ..."
}

The target URL must be HTTPS and publicly resolvable. Loopback, private-range and internal addresses are rejected with a 400 — a tunnel such as ngrok is the way to receive webhooks on a dev machine.

To stop deliveries, DELETE /webhooks/{id} with the id from the response.

A POST with a JSON body and two headers:

Header
X-1UP-Event The event name, e.g. order.created
X-1UP-Signature sha256=<hex> — an HMAC of the raw body, keyed with your secret
order.created
{
"id": 90412,
"event": "order.created",
"reference": "8KQ2-WRMT-9P4D",
"customerId": 412,
"value": 25.00,
"currency": "GBP",
"country": "GB",
"sourceType": "api",
"externalReference": "q4-bonus-2026-aisha",
"giftSelectionType": "gift",
"giftProductId": 184,
"giftsCollectionId": null,
"wrappingKey": "default",
"fromName": "The Northwind team",
"deliveryMode": "send",
"lineCount": 1,
"createdBy": 9031,
"creationDate": "2026-10-01T09:14:22Z"
}

Compute HMAC-SHA256 over the raw request body with your subscription secret, hex-encode it lowercase, and prefix it with sha256=. Compare against X-1UP-Signature using a constant-time comparison.

import crypto from 'node:crypto'
function verify(rawBody, header, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(header ?? '')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
  1. Return 2xx quickly. Any 2xx marks the delivery as done. Acknowledge first and do your work asynchronously — a slow handler will time out and be retried.

  2. Return 5xx or 429 to be retried. 1UP backs off exponentially — roughly 4 seconds, then 16, then a minute, capping at 6 hours — with jitter, for up to 12 attempts before giving up.

  3. Return 410 Gone to unsubscribe. 1UP deletes the subscription. Use this when the endpoint is permanently retired; any other 4xx just drops that one delivery.

Delivery is at-least-once. A handler that times out after doing its work will be retried, and you will see the same event again. Make your handler idempotent — the id field is stable per order, so it works as a deduplication key.

If you cannot host a public endpoint, GET /orders returns your most recent orders in the same shape as the order.created payload, newest first. Webhooks are the better option where you have the choice.