If you're integrating a gift card API for the first time, the vendor demo will look great. The sandbox will work on the first try. You'll think this is going to be a clean two-week sprint.
Then you ship to production and discover the same three problems every team hits:
Duplicate orders because retries weren't idempotent.
Missed transactions because webhooks didn't arrive.
A finance team that can't reconcile your numbers against the vendor's numbers.
I write a lot about gift card infrastructure (full disclosure: I work on a product in the space). The piece below is the developer-shaped version of a longer evaluation guide we published — focused on what actually breaks at runtime and what to ask vendors before you commit.
- Idempotency, or you'll double-charge your customers If your POST /orders call times out, you don't know whether the order went through. So you retry. Without an idempotency key, you've now ordered the card twice and charged the customer twice.
Every serious vendor supports idempotency keys. The contract is simple: you send a unique key with the request, and if the vendor sees the same key again they return the original response without re-executing.
POST /api/v1/orders
Content-Type: application/json
{
"idempotency_key": "cust_12345_order_789_ts_1681234567",
"customer_id": "cust_12345",
"brand_id": "AMAZON_US",
"amount": 50,
"currency": "USD"
}
A few things people get wrong here:
Generate the key before the first attempt, not before each attempt. If you regenerate on retry, you've defeated the whole mechanism.
Persist it. If your process crashes mid-retry, you need to recover the same key from durable storage — not regenerate one.
Scope it to the operation, not the user. A UUID per order is fine. customer_id_order_id_timestamp is also fine. customer_id alone is not.
If a vendor doesn't support idempotency keys, that's a dealbreaker. Don't let "we'll handle retries on our side" satisfy you — you're the one carrying the risk if they get it wrong.
- Verify webhook signatures, then build a poller anyway Webhooks are how you find out an order completed in real time. Two failure modes you need to plan for:
Spoofed webhooks. Anyone who can guess your endpoint can POST to it. Always verify the HMAC signature.
Missed webhooks. Even at 99.9% delivery, 10K transactions a day means ~10 missing events daily. Over a year, that's thousands of orders in an unknown state.
Signature verification is straightforward:
const crypto = require('crypto');
function verifyWebhook(req, secret) {
const body = JSON.stringify(req.body);
const signature = req.headers['x-signature-256'];
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Use timingSafeEqual, not ===. Timing attacks against naive string comparison on signatures are real and trivially exploitable.
For the missed-webhook problem, the answer is to never trust webhooks as your source of truth. Treat them as a low-latency hint that something happened, then confirm by querying the vendor's reconciliation API on a schedule. Webhooks are a cache; the API is the database.
Questions worth asking your vendor before you sign:
What's the published webhook delivery SLA?
What's the retry policy when your endpoint returns non-2xx?
Are events signed?
Is there a reconciliation endpoint I can query to backfill missed events?
If any of those is "no" or "let me get back to you," that tells you something about how the platform is built.
- Reconciliation belongs in your database, not theirs The vendor dashboard is not your system of record. Your database is.
The pattern that actually works in production:
Every outbound API call writes to your orders table first, with the idempotency key as the primary correlation field.
A nightly job pulls the vendor's reconciliation file (or hits their reconciliation API) for the last 24h.
Match every vendor record to a row in your table by idempotency key.
Anything in the vendor's data without a matching row in yours = a missed webhook. Backfill it.
Anything in your table without a matching vendor record = a failed order that needs to be retried or refunded.
Compare daily totals (count and amount). Post the clearing entry to your GL.
If your finance team is spending four hours a week reconciling gift card transactions, the integration chose complexity over clarity. The reconciliation API should make that a 30-minute spot-check.
What to look for in a vendor's reconciliation surface:
Transaction-level records, not aggregated daily totals.
Your internal reference ID echoed back on every record.
An idempotent, queryable endpoint — not "we'll email you a CSV at 2am."
Status fields you can map directly to GL states (pending, fulfilled, refunded, failed).
Bulk endpoint vs. API: pick on volume, not vibes
One more thing worth flagging, because teams burn weeks on it: not every gift card integration needs to be an API integration.
Bulk / CSV upload is the right answer if you're sending cards weekly or monthly, your campaigns are templated, and a human is in the loop. Don't over-engineer.
API is the right answer if you're embedded in a customer-facing product, fulfillment is real-time, you're doing 100+ transactions a day, or reconciliation has to be automated.
Many teams start with bulk and migrate to API once they cross ~500 orders/month. If your vendor doesn't support both on the same backend, you'll hit a painful re-platform later. Worth checking up front.
TL;DR for the procurement call
Three questions to put to vendors before you commit to an integration:
Do you support idempotency keys, and what's the retention window?
Are webhooks signed, and what's your published delivery SLA?
Is there a reconciliation API I can query at any time for transaction-level records?
Three "yes" answers won't guarantee a clean integration. Three "no" answers guarantee a painful one.
If you want the longer version with the finance/procurement angle — settlement timing, FX markup, PCI scope, vendor comparison — the full piece is here: Gift Card API Explained: https://www.gifq.com/blog/gift-card-api-explained
Top comments (0)