Guides
Idempotency
Make reward sends safe to retry.
Network calls fail. If a POST /v1/rewards times out, you can't tell whether
the reward went out — and blindly retrying risks paying twice. Idempotency
keys fix this.
How it works
Send a unique Idempotency-Key header with the request:
curl -X POST https://paybilt-rewards-api.fly.dev/v1/rewards \
-H "Authorization: Bearer $REWARDLY_KEY" \
-H "Idempotency-Key: order-1042-reward" \
-H "Content-Type: application/json" \
-d '{ "type": "CHOICE", "valueCents": 5000, "recipient": { "email": "casey@example.com", "firstName": "Casey" } }'- First request with a key: processed normally, returns
201. - Any retry with the same key: the original response is replayed with
200— no new reward, no second charge to your wallet.
Keys are scoped to your merchant account and capped at 100 characters.
Choosing keys
Derive the key from the business event, not the attempt:
order-1042-reward— good: retries of the same order collapsecrypto.randomUUID()per attempt — bad: retries create duplicates
Retry recipe
async function sendReward(input: unknown, key: string) {
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await fetch("https://paybilt-rewards-api.fly.dev/v1/rewards", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.REWARDLY_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(input),
signal: AbortSignal.timeout(15_000),
});
if (res.status === 402) throw new Error("top up required"); // don't retry
if (res.ok) return await res.json();
} catch {
// timeout or network error: safe to retry thanks to the key
}
await new Promise((r) => setTimeout(r, 1000 * 2 ** attempt));
}
throw new Error("gave up after 3 attempts");
}