Cashback Claim
How to correctly process cashback claims.
How it works
The cashback claim flow is always initiated by your backend. The widget plays no role in triggering a claim β it is purely for display. The sequence is straightforward:
The userRef β the critical link
The userRef you include in a cashback claim must be exactly the same value you use as userRef in the JWT assertion when that user opens the widget. This is how Fruga connects the claim to the right userβs wallet and display.
Idempotency
Every claim request requires a partnerEventId β a unique string you generate for each event. Fruga uses this to make the endpoint idempotent: if the same partnerEventId is submitted more than once, Fruga returns the original response without creating a duplicate payout.
This protects you in real production scenarios β network timeouts, retry logic, or double-submission bugs will not result in a user being paid twice. The safest approach is to derive the partnerEventId from something that is naturally unique in your system, such as a policy transaction ID or order reference.
Redemption context
The redemptionContext field tells Fruga what kind of event triggered the claim. This is used for reporting, display in the widget, and future analytics. Set it to the value that most accurately describes the event.
| Value | When to use it |
|---|---|
NEW_POLICY | A user purchased a new insurance policy through your platform |
POLICY_ADDON | A user added a supplementary product or add-on to an existing policy |
CLAIM_EXCESS | A user paid a claim excess and is receiving cashback on that amount |
OTHER | Anything that does not fit the above β must be accompanied by a redemptionContextNotes string explaining the event |
Payout routing β how your configuration determines what happens next
Once Fruga receives and validates a claim, it determines how to route the payout based on your partner-level configuration, set up by Fruga during onboarding. You do not pass a routing destination in the claim request itself β the routing is always derived from your configuration.
There are two routing modes:
Option A β Pay the user directly (TO_USER)
Fruga pays the end user directly to their bank account. This is the default mode for most integrations. What happens after the claim depends on whether the user has already added their bank details in the widget.
| Scenario | Transaction status | What the user sees |
|---|---|---|
| User has bank details on file | PROCESSING | Widget shows the payout is on its way. No action required. |
| User has no bank details on file | ACTION_REQUIRED | Widget prompts the user to add their bank account details before the payout can proceed. |
When a user in ACTION_REQUIRED status adds their bank details via the widget, the payout is automatically unblocked and moves to PROCESSING. No further action is needed from your side.
Option B β Pay the partner (TO_PARTNER)
Fruga records the payout entitlement and settles the amount with you as the partner β either via periodic batch settlement or automated transfer, depending on your agreed arrangement. You are then responsible for passing the funds to your end user through whatever mechanism your platform uses.
| Scenario | Transaction status | What the user sees |
|---|---|---|
| Claim received, settlement pending | CREATED or PROCESSING | Widget shows the cashback has been recorded and is being processed. No bank details prompt is shown. |
| Settlement confirmed | PAID | Widget shows the payout as complete. |
In TO_PARTNER mode, the widget never asks the user for bank details β that interaction is handled entirely within your platform. The widget simply reflects the claim and its status.
Transaction status reference
Regardless of routing mode, every claim creates a payout transaction in Frugaβs system. Here are all the statuses a payout transaction can have and what they mean.
| Status | Meaning |
|---|---|
CREATED | Claim recorded. Payout is queued but not yet initiated β typically used in TO_PARTNER batch settlement scenarios. |
ACTION_REQUIRED | TO_USER only. User needs to add bank details before the payout can proceed. The widget shows a prompt. |
PROCESSING | Payout has been initiated and sent to the payment processor or settlement system. |
PAID | Payout confirmed and complete. |
FAILED | Payout attempt failed β for example, invalid bank details or a rejected transfer. The transaction may be retried depending on the failure reason. |
CANCELLED | Payout was cancelled before completion. |
A complete example
The following shows a full cashback claim request in Node.js, including HMAC signing. This example uses the NEW_POLICY context and includes an optional policy reference.
import crypto from 'crypto';
const submitCashbackClaim = async ({ userRef, amount, eventId, policyId }) => {
const partnerKey = process.env.FRUGA_PARTNER_KEY;
const secret = process.env.FRUGA_SIGNING_SECRET;
const timestamp = Math.floor(Date.now() / 1000).toString();
const path = '/cashback/claim';
const body = JSON.stringify({
partnerEventId: eventId,
userRef,
amount,
redemptionContext: 'NEW_POLICY',
reference: { policyId },
});
// Build the signature
const bodyHash = crypto
.createHash('sha256')
.update(body)
.digest('hex');
const signingString = `${timestamp}.POST.${path}.${bodyHash}`;
const signature = crypto
.createHmac('sha256', secret)
.update(signingString)
.digest('base64');
const response = await fetch('https://api.fruga.com/cashback/claim', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Partner-Key': partnerKey,
'X-Partner-Timestamp': timestamp,
'X-Partner-Signature': signature,
},
body,
});
if (!response.ok) {
throw new Error(`Claim failed: ${response.status}`);
}
return response.status; // 201 created, 200 idempotent replay
};
Troubleshooting
| Error | Likely cause | Fix |
|---|---|---|
401 Unauthorized | Invalid HMAC signature | Verify your signing string is constructed as {timestamp}.POST./cashback/claim.{bodyHash}. Check that bodyHash is a SHA-256 hex digest of the raw body string, and that the body has not been modified between hashing and sending. |
409 Conflict | Same partnerEventId submitted with a different body | Do not reuse event IDs. If you need to correct a claim, contact Fruga β you cannot overwrite an existing event ID. |
200 OK (instead of 201) | Idempotent replay β the claim already exists | This is expected behaviour on retries. The original claim is returned unchanged. No duplicate payout is created. |
| Claim created but not visible in widget | userRef mismatch between claim and JWT assertion | Confirm the userRef in the claim exactly matches the userRef used in the widget assertion for that user. Both values are case-sensitive. |
User in ACTION_REQUIRED but already has bank details | Bank details were added after the claim was created | The payout should auto-advance when bank details are saved. If it does not, contact Fruga support with the partnerEventId. |