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 {FRUGA_API_URL}/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 {FRUGA_API_URL}/partner/user/status?userRef=user_123

Request headers

HeaderRequiredDescription
X-Partner-KeyYesYour NEXT_PUBLIC_PARTNER_ID for this environment - development and production have separate values
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 PARTNER_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 - the path only, with no host and no /api prefix).
  • 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 PARTNER_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.PARTNER_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.NEXT_PUBLIC_PARTNER_ID,
    'X-Partner-Timestamp': timestamp,
    'X-Partner-Signature': signature,
  };
};

const userRef = encodeURIComponent('user_123');
const url = `${process.env.FRUGA_API_URL}/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["PARTNER_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["NEXT_PUBLIC_PARTNER_ID"],
        "X-Partner-Timestamp": timestamp,
        "X-Partner-Signature": signature,
    }

user_ref = quote("user_123", safe="")
url = f"{os.environ['FRUGA_API_URL']}/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 productionEach environment has its own FRUGA_API_URL and its own set of credentials. Never mix a development NEXT_PUBLIC_PARTNER_ID / PARTNER_SECRET with a production host, or the reverse.