Cashback Claim

How to correctly process cashback claims.

πŸ’‘
This is a guide to help you understand the end-to-end flow. For the exact request format, headers, and response codes, see Partner API β†’ Cashback Claim.

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:

1
A qualifying event occurs on your platform
For example: a user purchases a new policy, applies an add-on, or pays a claim excess. The decision about what constitutes a qualifying event is yours β€” Fruga acts on whatever you tell it.
2
Your backend calls POST /cashback/claim
You send a signed server-to-server request with the user reference, the cashback amount, and the event context. Fruga validates the request, verifies your identity via HMAC signature, and records the claim.
3
Fruga creates a payout transaction and routes it
Based on your partner configuration, Fruga determines how the money should move β€” either directly to the user's bank account, or to you as the partner. The widget updates automatically to reflect the new balance and payout status.

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.

⚠️
A mismatch between the `userRef` in a claim and the `userRef` in the widget assertion is the most common cause of claims not appearing in a user's widget. Use a stable, unique identifier from your system β€” typically a user ID β€” and use it consistently in both places.

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.

⚠️
Submitting the same `partnerEventId` with a different body returns a `409 Conflict`. The idempotency key is tied to the exact claim β€” it cannot be reused for a different amount or user.

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.

ValueWhen to use it
NEW_POLICYA user purchased a new insurance policy through your platform
POLICY_ADDONA user added a supplementary product or add-on to an existing policy
CLAIM_EXCESSA user paid a claim excess and is receiving cashback on that amount
OTHERAnything 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.

ScenarioTransaction statusWhat the user sees
User has bank details on filePROCESSINGWidget shows the payout is on its way. No action required.
User has no bank details on fileACTION_REQUIREDWidget 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.

πŸ’‘
In TO_USER mode, you have no responsibility for the last-mile payment. Fruga handles the bank transfer, confirmation, and any failure handling. The widget reflects the current status to the user at all times.

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.

ScenarioTransaction statusWhat the user sees
Claim received, settlement pendingCREATED or PROCESSINGWidget shows the cashback has been recorded and is being processed. No bank details prompt is shown.
Settlement confirmedPAIDWidget 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.

StatusMeaning
CREATEDClaim recorded. Payout is queued but not yet initiated β€” typically used in TO_PARTNER batch settlement scenarios.
ACTION_REQUIREDTO_USER only. User needs to add bank details before the payout can proceed. The widget shows a prompt.
PROCESSINGPayout has been initiated and sent to the payment processor or settlement system.
PAIDPayout confirmed and complete.
FAILEDPayout attempt failed β€” for example, invalid bank details or a rejected transfer. The transaction may be retried depending on the failure reason.
CANCELLEDPayout 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

ErrorLikely causeFix
401 UnauthorizedInvalid HMAC signatureVerify 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 ConflictSame partnerEventId submitted with a different bodyDo 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 existsThis is expected behaviour on retries. The original claim is returned unchanged. No duplicate payout is created.
Claim created but not visible in widgetuserRef mismatch between claim and JWT assertionConfirm 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 detailsBank details were added after the claim was createdThe payout should auto-advance when bank details are saved. If it does not, contact Fruga support with the partnerEventId.