Guarantees

Errors

Every error has a stable code, an HTTP status, a plain-language message and a request id. Branch on the code — messages may be reworded.

422 Unprocessable Content
{
  "error": {
    "code": "RECIPIENT_UNVERIFIED",
    "message": "@orbit — unable to verify payout wallet.",
    "request_id": "req_h3k3v4ya2gkbxh3m",
    "details": { "recipient": "@orbit", "state": "UNVERIFIED" }
  }
}
  • code — stable, machine-readable; the tables below are exhaustive.
  • message — safe to show to a person. Never a raw RPC or database error.
  • request_id — also in the x-request-id header. Quote it to support.
  • details — structured context when there is some (the field that failed, the limit that was hit, the confirmations required).

Payment errors

RECIPIENT_NOT_FOUND404
“We couldn't find that Fomo identity.”
Check the spelling. Resolve only finds identities FomoPay knows.
RECIPIENT_UNVERIFIED422
“Unable to verify this recipient's payout wallet.”
The recipient must finish verifying their payout wallet (/claim). Retry later with the same key.
NO_PAYOUT_DESTINATION422
“This identity hasn't set up a verified payout wallet yet.”
Ask the recipient to make their handle FomoPay Ready.
UNSUPPORTED_ASSET422
“That asset isn't supported.”
Use USDG or ETH (vaults: USDG only). There is no official USDC on Robinhood Chain.
UNSUPPORTED_NETWORK422
“This recipient can't receive on this network.”
FomoPay settles on Robinhood Chain only; this recipient's wallet is elsewhere.
INSUFFICIENT_FUNDS422
“Insufficient balance.”
The paying wallet can't cover amount + gas. Fund it and retry.
PAYMENT_LIMIT_EXCEEDED422
“This payment is over a configured limit.”
Over a per-payment, daily, project, key or recipient limit. details names which.
DUPLICATE_PAYMENT409
“This payment was already created.”
Same idempotency key, different body. Don't retry — use a new key for a new payout.
NETWORK_UNAVAILABLE503
“The network is unavailable right now. Try again shortly.”
Robinhood Chain RPC is unreachable. Retry with backoff and the same key.
TRANSACTION_FAILED422
“Transfer failed.”
The transfer reverted or failed verification. The payment is FAILED; create a new one if appropriate.

Platform errors

RECIPIENT_SUSPENDED422
“This identity can't receive payments right now.”
Don't retry. Contact the recipient another way.
VALIDATION_FAILED400
“Some fields are invalid.”
Fix the request. details.issues lists each field.
UNAUTHENTICATED401
“Sign in to continue.”
Missing, malformed or revoked API key.
FORBIDDEN403
“You don't have permission to do that.”
Not allowed — e.g. a test key against mainnet, or a role without permission.
INSUFFICIENT_SCOPE403
“This API key is missing a required scope.”
Add the scope in details.required to the key (or use another key).
NOT_FOUND404
“Not found.”
No such object in this project.
CONFLICT409
“Something went wrong.”
The object changed underneath you. Re-read and retry.
IDEMPOTENCY_KEY_REUSED409
“This idempotency key was already used with a different request.”
Same as DUPLICATE_PAYMENT for non-payment objects.
INVALID_STATE409
“This action isn't possible in the current state.”
The action doesn't apply in the current status (e.g. canceling a submitted payment).
RATE_LIMITED429
“Too many requests. Slow down and try again.”
Wait Retry-After seconds, then retry with the same key.
FEATURE_DISABLED403
“This feature isn't enabled.”
That capability is switched off on this deployment.
MAINNET_DISABLED403
“Mainnet payments are not enabled yet.”
This deployment hasn't enabled mainnet payments yet.
SIGNATURE_REJECTED422
“Wallet rejected signature.”
The wallet user declined to sign. Nothing was sent.
BLOCKHASH_EXPIRED409
“The transaction expired before it was sent. Review and sign again.”
The prepared transaction went stale before it was sent. Prepare it again.
VAULT_INSUFFICIENT422
“The reward vault doesn't have enough available balance.”
Top up the vault wallet.
VAULT_ALLOWANCE_EXCEEDED422
“This payout is over the vault's approved allowance.”
Raise the approved allowance from the vault wallet.
VAULT_NOT_ACTIVE409
“The reward vault isn't active.”
Finish vault setup, unpause it, or re-approve the signer.
APPROVAL_REQUIRED202
“This payment needs approval before it's sent.”
Not a failure: the payment exists and waits for project approvers.
RISK_HOLD202
“This payment is on hold for a security review.”
Not a failure: the payment exists and is held (see hold_until).
COMPLIANCE_BLOCKED451
“This payment can't be processed.”
Can't be processed. Don't retry.
CONFIRMATION_REQUIRED428
“Please confirm the recipient before sending.”
Show the warning to a human, then retry with the confirmations listed in details.required.
UPSTREAM_UNAVAILABLE503
“Identity lookup is temporarily unavailable.”
The identity provider is unreachable. Retry shortly.
INTERNAL500
“Something went wrong.”
Our fault. Retry with the same key; quote request_id if it persists.

SDK-only errors

Raised by @fomopay/sdk when there is no usable API response. status is 0.

CodeWhen
NETWORK_ERRORThe API couldn't be reached after all retries (DNS, TLS, connection reset).
TIMEOUTNo response within timeoutMs on every attempt.
ABORTEDYour AbortSignal fired.
INVALID_AMOUNTThe amount was rejected before sending: not a positive decimal, or more decimals than the asset has (0.1 + 0.2 for USDG).
INVALID_RESPONSEA 2xx response without a JSON body (usually a proxy in the way).
WEBHOOK_SIGNATURE_INVALIDFomoPay.webhooks.verify: missing/garbled header or no matching v1 signature.
WEBHOOK_TIMESTAMP_EXPIREDFomoPay.webhooks.verify: the signed timestamp is outside the 5-minute window.

Handling errors

handle.ts
import { FomoPay, FomoPayError } from "@fomopay/sdk";

try {
  await fomopay.send({ to: "@lyra", amount: 100, asset: "USDG", idempotencyKey: "FOMORUN_42_WINNER" });
} catch (e) {
  if (!(e instanceof FomoPayError)) throw e;
  switch (e.code) {
    case "RECIPIENT_UNVERIFIED":
    case "NO_PAYOUT_DESTINATION":
      return askWinnerToGetFomoPayReady(); // retry later with the SAME key
    case "VAULT_INSUFFICIENT":
      return alertFinance(e.requestId);
    default:
      if (e.retryable) return scheduleRetry(); // 429 / 5xx / network — same key is safe
      throw e;
  }
}
Which errors are safe to retry?

429, 5xx and network failures — always with the same idempotency key. The SDK already does this for you (maxRetries, default 2, honouring Retry-After). Business errors (4xx) won't change by retrying the same request; fix the cause first.