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.
| Property | Value |
|---|---|
| Authentication | HMAC-SHA256 request signing (same pattern as Cashback Claim) |
| Content type | No request body. Successful responses use application/json. |
| Caller | Partner 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.
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
| Parameter | Required | Description |
|---|---|---|
userRef | Yes | Your 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
| Header | Required | Description |
|---|---|---|
X-Partner-Key | Yes | Your partner environment key - pk_test_... for sandbox, pk_live_... for production |
X-Partner-Timestamp | Yes | Current Unix timestamp in seconds (UTC). Requests more than 300 seconds from Fruga’s server time are rejected. |
X-Partner-Signature | Yes | Base64-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/apiprefix onapi.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.
/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
| Field | Type | Description |
|---|---|---|
userRef | string | Echo of the partner user reference. |
available | number | Funds available for redemption now - use this for real-time redemption decisions. |
pending | number | Funds earned but not yet cleared for redemption. |
lifetimeEarned | number | Total positive earnings ever credited to the wallet. |
lifetimeRedeemed | number | Total payouts or redemptions completed. |
lastRedemption | object or null | Latest redemption transaction, if any. See nested fields below. |
updatedAt | string (date-time) | When this status snapshot was last updated on Fruga’s side. |
lastRedemption object (when present)
| Field | Type | Description |
|---|---|---|
transactionId | integer | Fruga transaction identifier. |
amount | number | Redemption amount. |
status | string | One of: APPROVED, DECLINED, PENDING_RETAILER, PENDING_PROVIDER, PENDING_BANK, CREATED, ACTION_REQUIRED, PROCESSING, PAID, FAILED, CANCELLED. |
redemptionContext | string | One of: NEW_POLICY, POLICY_ADDON, CLAIM_EXCESS, OTHER. |
createdAt | string (date-time) | When the transaction was created. |
Errors
| Status | Typical meaning |
|---|---|
400 Bad Request | Malformed request or validation failure (for example missing userRef). |
401 Unauthorized | Invalid signature, expired timestamp, or unknown partner key - same troubleshooting steps as Cashback Claim. |
403 Forbidden | Authenticated partner context cannot access this user or operation. |
500 Internal Server Error | Unexpected 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
| Topic | Guidance |
|---|---|
| Caching | Treat responses as near real-time, not a long-lived cache. If you cache, use a short TTL so redemption limits stay accurate. |
| Rate limits | If your tier applies rate limits, use backoff on repeated errors. |
| Sandbox vs production | Use pk_test_... / sk_test_... against sandbox hosts only; production keys only against production. |