Implementation worksheet · 5 min read
A Reward Eligibility and Duplicate-Claim Test Matrix
Test nine cases before any reward is payable: same user claiming twice, two sessions claiming simultaneously, a retry after a timeout mid-claim, a claim after the reward expires, a claim by an ineligible user, a self-referral through a second account, a refunded or reversed qualifying action, a claim during a rule change, and a replayed webhook or callback from the payout provider. Every one of them is normal traffic rather than an attack. The rule underneath all nine: a reward grant must be idempotent on a claim key, and eligibility must be evaluated inside the same transaction that records the grant — checking first and granting after is a race, and concurrency will find it.
Reward systems are written for the happy path and discovered by users who have time. The expensive failures are not clever fraud; they are ordinary retries and double-clicks that the code treats as separate events.
Put it into practice
1. Give every claim a key
Derive it from user, reward type and qualifying event. The grant is idempotent on that key, so a retry resolves to the same grant rather than a second one.
2. Evaluate eligibility inside the transaction
Check-then-grant is a race: two concurrent requests both pass the check before either writes. Either use a single conditional write, or lock on the claim key.
3. Define reversal explicitly
If the qualifying action is refunded, cancelled or charged back, is the reward clawed back, left, or offset against future rewards? Write it down before you have to decide it about a real person.
4. Decide the self-referral rule and say it publicly
Same payment method, same device, same household? Choose, publish it in the program terms, and enforce it consistently. Silent blocking generates support tickets from legitimate users and teaches nothing to the others.
5. Make the payout callback idempotent too
Providers retry. A duplicated success callback that credits again turns one payout into two, and that money is usually not recoverable.
Reward test matrix
Copy this structure into your review document and record your observed result for each row.
| Case | Setup | Expected | Verified |
|---|---|---|---|
| Double claim | same user, twice | one grant | |
| Concurrent claim | two sessions at once | one grant | |
| Retry after timeout | interrupt mid-claim | one grant | |
| Expired reward | claim after expiry | rejected, explained | |
| Ineligible user | criteria not met | rejected, explained | |
| Self-referral | second account, same person | blocked per published rule | |
| Reversed action | refund after grant | per reversal policy | |
| Rule change mid-claim | criteria edited | old rule honoured | |
| Replayed callback | provider retries | no second payout |
A failure worth checking
The double payout: a user taps claim, the request times out at the payout provider, they tap again, and both requests succeed because the grant has no idempotency key. Two rewards, one qualifying action, no error anywhere. At small scale it is a rounding error; at campaign scale it is a finance conversation and a program someone switches off.
Common questions
Is this over-engineering for a small program?
The nine cases are one afternoon of tests. A single double-payout incident costs more than that in reconciliation time alone, and the loss of finance's confidence usually outlasts the program.
How strict should self-referral rules be?
Strict enough to stop the obvious cases, published so people know, and reviewable so false positives can be fixed by a human. Aggressive silent blocking punishes legitimate users who share a household or an office IP.
Basis and scope
This is a proposed implementation method using illustrative examples, not a measured benchmark or a customer case study. Prepared with AI assistance. Validate product-specific behavior against current documentation and your own test environment.