Webhooks Integration Guide¶
Gift Card Hero sends HTTP POST requests to your server when specific gift card events occur. Use webhooks to keep external systems in sync with gift card activity in real time.
Setup¶
- Go to For developers in the left navigation
- Tick Enable REST API and save. Webhook settings appear only while the REST API is on
- Click Generate Secret to create a webhook signing secret

The secret is shown only once
Copy the secret and store it securely. If you lose it, click Regenerate Secret to create a new one. The old secret stops working at once, so update your server too.
- Enter your endpoint URLs for each webhook type you want to receive, then click Save in the save bar

A new or changed endpoint URL must start with https:// and point to a server reachable from the internet. Addresses inside a private network — localhost, 127.0.0.1, 10.x.x.x, 192.168.x.x and similar — are refused when you save. A URL that was saved before this rule keeps working until you change it, but it is still only called if it points to a public address. To test against a local server, expose it through a tunnel such as ngrok and use the public https URL it gives you.
Event types¶
Gift Card Hero has three webhook URL fields:
| Event | Topic header value | When it fires |
|---|---|---|
| Gift card order | gift_card_order |
Gift Card Hero processes the gift cards of an order. The payload lists the cards that belong to the order |
| Gift card payment | gift_card_payment |
An order is created that was paid, fully or partly, with a gift card |
| Gift card balance change | gift_card_balance |
Not sent at the moment |
Balance change webhook
The Gift card balance webhook URL field is on the page, but Gift Card Hero does not send this event yet. To follow balance changes today, poll GET /gift-cards/transactions or use the gift_card_payment event for payments.
Webhooks are sent only while Enable REST API is on and the URL for that event is filled in.
Delivery¶
- Method: POST
- Timeout: 6 seconds
- Attempts: Up to 5 delivery attempts in total (the first one and up to 4 retries), with exponentially growing delays that start at roughly 30 to 60 seconds
- Redirects: Not followed. A
3xxresponse counts as a failed delivery, so use the final URL of your endpoint.
If your endpoint does not return a 2xx response within 6 seconds, the delivery is considered failed and will be retried.
Headers¶
Every webhook request includes the following headers:
| Header | Value |
|---|---|
Content-Type |
application/json |
User-Agent |
GiftHero-Webhooks/1.0 |
X-GiftHero-Topic |
The event type (e.g., gift_card_order) |
X-GiftHero-Signature |
HMAC-SHA256 signature for verification. Sent only when you have generated a webhook secret |
Signature verification¶
The X-GiftHero-Signature header contains an HMAC-SHA256 signature, hex-encoded and prefixed with sha256=.
Important: The signature is computed over a JSON object containing only
{ type, timestamp }from the payload body — NOT the entire request body.
Verification steps¶
- Parse the request body and extract the
typeandtimestampfields - Construct the signing payload:
JSON.stringify({ type, timestamp }) - Compute the HMAC-SHA256 using your webhook secret as the key
- Hex-encode the result and prefix with
sha256= - Use a constant-time comparison to compare with the
X-GiftHero-Signatureheader
Example (Node.js)¶
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const signingPayload = JSON.stringify({
type: body.type,
timestamp: body.timestamp
});
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(signingPayload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Payload schemas¶
Every request body has the same envelope. The event details are inside data:
{
"type": "gift_card_order",
"timestamp": "2026-03-15T10:30:00.000Z",
"data": { }
}
Gift card order (gift_card_order)¶
{
"type": "gift_card_order",
"timestamp": "2026-03-15T10:30:00.000Z",
"data": {
"order": {
"id": 5551234567890,
"name": "#1042",
"email": "buyer@example.com",
"createdAt": "2026-03-15T10:30:00.000Z",
"lineItems": [
{
"id": 11198765432100,
"title": "Gift Card",
"quantity": 1,
"sku": "GC-50",
"variantId": 44556677
}
]
},
"giftCards": [
{
"id": 987654321,
"balance": "50.00",
"currency": "USD",
"lastCharacters": "ab12",
"initialValue": "50.00",
"customer": { "id": 123, "email": "buyer@example.com", "name": "Alex Buyer" },
"recipient": { "id": 456, "email": "friend@example.com", "name": "Sam Friend" },
"code": "abcd1234efgh5678",
"lineItemId": 11198765432100,
"sku": "GC-50"
}
]
}
}
Gift card payment (gift_card_payment)¶
Each entry in giftCards also carries the transaction that paid for the order.
{
"type": "gift_card_payment",
"timestamp": "2026-03-16T14:22:00.000Z",
"data": {
"order": {
"id": 5551234567891,
"name": "#1043",
"email": "shopper@example.com",
"createdAt": "2026-03-16T14:22:00.000Z",
"lineItems": [
{ "id": 11198765432200, "title": "Running Shoes", "quantity": 1, "sku": "SHOE-9" }
]
},
"giftCards": [
{
"id": 987654321,
"balance": "0.00",
"currency": "USD",
"lastCharacters": "ab12",
"initialValue": "50.00",
"customer": null,
"recipient": null,
"transaction": {
"id": "gid://shopify/OrderTransaction/123456789",
"amount": "50.00",
"currency": "USD",
"type": "debit",
"createdAt": "2026-03-16T14:22:00.000Z"
}
}
]
}
}
Field notes¶
data.orderanddata.giftCardsare the fields to build on.dataalso repeats some order fields at its top level (orderId,orderName,orderEmail,createdAt,lineItems) and carries internal fields (storeId,shop,jobType). Do not rely on those.balanceis the card's balance when the webhook is built, not when the order was placed.codeis the full gift card code. It is included only when Gift Card Hero has the code on record, and omitted otherwise. Treat it like a password and make sure your endpoint is secured.lineItemIdandskuare included when the card can be matched to an order line.lineItemsentries carryid,title,quantity,skuandvariantId(no price).- If a card's details cannot be loaded, its entry is reduced to
id, zero amounts and an emptylastCharacters. Re-read the card with the REST API when you see that.
Best practices¶
- Return 2xx quickly — acknowledge the webhook immediately and process the payload asynchronously. Responses taking longer than 6 seconds are treated as failures.
- Handle duplicates — the same event may be delivered more than once due to retries. Use the order and gift card IDs in the event data to deduplicate. The
timestampis the time of each attempt, so it is not a stable key. - Verify HMAC signatures — always verify the
X-GiftHero-Signatureheader before processing any webhook payload. Reject requests with invalid signatures. - Use idempotency keys — design your webhook handler to be idempotent so that processing the same event twice produces the same result.
- Monitor failures — if your endpoint is consistently failing, Gift Card Hero stops retrying after 5 attempts in total. Check your server logs and endpoint health regularly.