Rewardly Docs
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 collapse
  • crypto.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");
}

On this page