User status

Fetch a user's Relay wallet status from your backend in real time, including available credit for redemption, using the same HMAC-signed Partner API pattern as Cashback Claim. Server-to-server only; never call from the browser.

GET https://api.fruga.com/partner/user/status
PropertyValue
AuthenticationHMAC-SHA256 request signing (same pattern as Cashback Claim)
Content typeNo request body. Successful responses use application/json.
CallerPartner backend only, not the browser or widget

Why use this endpoint

Your backend may need an authoritative, up-to-date view of a user’s Relay balances. For example, you might decide whether they can redeem an offer, display savings alongside your own UI, or reconcile state after a purchase flow. The widget keeps users informed inside Relay, but this API lets your servers query Fruga directly so those decisions stay in sync.

The response highlights available: funds that are available for redemption now. Use it whenever you need a real-time signal for redemption eligibility or messaging on your platform.

⚠️
The userRef you pass must be the same stable identifier you use in the JWT assertion when that user opens the widget (and the same value you send on cashback claims). A mismatch means you may query or credit the wrong logical user.

Query parameters

ParameterRequiredDescription
userRefYesYour internal identifier for the user, matching the widget assertion and claims. Case-sensitive.

Example

GET https://api.fruga.com/partner/user/status?userRef=user_123

Request headers

HeaderRequiredDescription
X-Partner-KeyYesYour partner environment key - pk_test_... for sandbox, pk_live_... for production
X-Partner-TimestampYesCurrent Unix timestamp in seconds (UTC). Requests more than 300 seconds from Fruga’s server time are rejected.
X-Partner-SignatureYesBase64-encoded HMAC-SHA256 of the canonical signing string (see below).

Signing the request

Signing follows the same rules as Cashback Claim: HMAC-SHA256 with your Signing Secret, a dotted canonical string, and a lowercase hex SHA-256 digest of the exact request body bytes.

For this endpoint:

  • HTTP method is GET.
  • Path in the signing string is /partner/user/status (production and sandbox follow the same path convention as /cashback/claim - no /api prefix on api.fruga.com).
  • There is no request body: hash the empty string ("") so the body digest is the SHA-256 hex of empty input (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855).

Construct:

{timestamp}.GET./partner/user/status.{bodyHash}

Then HMAC-SHA256 that string with your Signing Secret and Base64-encode the result for X-Partner-Signature.

💡
Some gateway deployments expose an /api prefix (for example on Railway during development). Always use the **path segment your environment actually calls** when building the signing string, and keep the full URL consistent with that path. If signatures fail after double-checking the secret and clock, contact Fruga integrations - we can confirm the canonical path for your environment.

Signing example (Node.js)

import crypto from 'crypto';

const buildSignedGetHeaders = (pathForSigning) => {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const secret = process.env.FRUGA_SIGNING_SECRET;
  const body = ''; // GET - empty body
  const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
  const signingString = `${timestamp}.GET.${pathForSigning}.${bodyHash}`;

  const signature = crypto
    .createHmac('sha256', secret)
    .update(signingString)
    .digest('base64');

  return {
    'X-Partner-Key': process.env.FRUGA_PARTNER_KEY,
    'X-Partner-Timestamp': timestamp,
    'X-Partner-Signature': signature,
  };
};

const userRef = encodeURIComponent('user_123');
const url = `https://api.fruga.com/partner/user/status?userRef=${userRef}`;

const response = await fetch(url, {
  method: 'GET',
  headers: buildSignedGetHeaders('/partner/user/status'),
});

Signing example (Python)

import hashlib, hmac, base64, time, os
from urllib.parse import quote
import requests

def build_signed_get_headers(path_for_signing: str) -> dict:
    timestamp = str(int(time.time()))
    secret = os.environ["FRUGA_SIGNING_SECRET"].encode()
    body = b""
    body_hash = hashlib.sha256(body).hexdigest()
    signing_string = f"{timestamp}.GET.{path_for_signing}.{body_hash}"
    signature = base64.b64encode(
        hmac.new(secret, signing_string.encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "X-Partner-Key": os.environ["FRUGA_PARTNER_KEY"],
        "X-Partner-Timestamp": timestamp,
        "X-Partner-Signature": signature,
    }

user_ref = quote("user_123", safe="")
url = f"https://api.fruga.com/partner/user/status?userRef={user_ref}"

response = requests.get(
    url,
    headers=build_signed_get_headers("/partner/user/status"),
)

Responses

Success (200 OK)

JSON body describing the user’s balances and related metadata. Monetary amounts are in GBP (consistent with Cashback Claim).

{
  "userRef": "user_123",
  "available": 46.2,
  "pending": 0.1,
  "lifetimeEarned": 120.5,
  "lifetimeRedeemed": 74.3,
  "lastRedemption": {
    "transactionId": 987654321,
    "amount": 12.0,
    "status": "PAID",
    "redemptionContext": "NEW_POLICY",
    "createdAt": "2026-04-20T14:32:01Z"
  },
  "updatedAt": "2026-04-28T09:15:00Z"
}

Fields

FieldTypeDescription
userRefstringEcho of the partner user reference.
availablenumberFunds available for redemption now - use this for real-time redemption decisions.
pendingnumberFunds earned but not yet cleared for redemption.
lifetimeEarnednumberTotal positive earnings ever credited to the wallet.
lifetimeRedeemednumberTotal payouts or redemptions completed.
lastRedemptionobject or nullLatest redemption transaction, if any. See nested fields below.
updatedAtstring (date-time)When this status snapshot was last updated on Fruga’s side.

lastRedemption object (when present)

FieldTypeDescription
transactionIdintegerFruga transaction identifier.
amountnumberRedemption amount.
statusstringOne of: APPROVED, DECLINED, PENDING_RETAILER, PENDING_PROVIDER, PENDING_BANK, CREATED, ACTION_REQUIRED, PROCESSING, PAID, FAILED, CANCELLED.
redemptionContextstringOne of: NEW_POLICY, POLICY_ADDON, CLAIM_EXCESS, OTHER.
createdAtstring (date-time)When the transaction was created.

Errors

StatusTypical meaning
400 Bad RequestMalformed request or validation failure (for example missing userRef).
401 UnauthorizedInvalid signature, expired timestamp, or unknown partner key - same troubleshooting steps as Cashback Claim.
403 ForbiddenAuthenticated partner context cannot access this user or operation.
500 Internal Server ErrorUnexpected server error - retry with backoff and contact Fruga if persistent.

Error bodies follow the common JSON error shape used elsewhere on the API (error, message, path, timestamp - exact fields may vary).


Operational notes

TopicGuidance
CachingTreat responses as near real-time, not a long-lived cache. If you cache, use a short TTL so redemption limits stay accurate.
Rate limitsIf your tier applies rate limits, use backoff on repeated errors.
Sandbox vs productionUse pk_test_... / sk_test_... against sandbox hosts only; production keys only against production.