API

Payments

Create a payment to a verified handle, follow it from CREATED to CONFIRMED, and read your history. Two ways to fund it: a person's own wallet (checkout) or your Reward Vault.

SourceWho signsAssetsUse it for
walletThe payer's own wallet, at the checkout link FomoPay returns. Non-custodial.USDG, ETHBounties, invoices, one-off payments a person approves
vaultFomoPay's payout signer, spending your vault's approved allowance.USDGAutomated payouts — rewards, prizes, referrals. Same as /v1/rewards

Create a payment

POST/v1/paymentsscope payments:create
// A bounty paid by a person: FomoPay returns a checkout link, the payer signs with their own wallet.
const payment = await fp.payments.create({
  recipient: "@lyra",
  amount: "0.05",
  asset: "ETH",
  network: "robinhood",
  memo: "Logo bounty",
});

payment.checkout_url; // https://fomopay.family/payment/pay_… → share it with whoever pays
Body
recipientstringrequired
The Fomo handle: @lyra. Resolved server-side; anything not VERIFIED is refused (states).
amountdecimal stringrequired
In whole units: "100", "0.05". USDG has 6 decimals, ETH 18. Never a float.
assetUSDG | ETHrequired
Token addresses are never accepted from requests — FomoPay uses its server-side registry (USDG is 0x5fc5…d168).
network"robinhood"optional
Optional; the only network. Anything else is 422 UNSUPPORTED_NETWORK.
source"wallet" | "vault"optional
Default wallet. vault requires vault_id.
vault_idstringoptional
The Reward Vault to pay from (vault_…).
sender_wallet0x addressoptional
Wallet payments: lock the checkout to one paying wallet. Omit to let any signed-in wallet pay.
memostring ≤ 280optional
Shown on the receipt (“Logo bounty”). Stored off-chain.
reasonstring ≤ 280optional
Why it was paid (“FomoRun #42 Winner”).
type"DIRECT" | "REWARD"optional
Defaults to DIRECT for wallet and REWARD for vault payments.
metadataobject ≤ 4 KBoptional
Your own keys and values, returned on the payment and in webhooks.
visibilityprivate | unlisted | publicoptional
Receipt visibility. Default unlisted (anyone with the link). Amounts are never published unless you choose public.
confirmationsobjectoptional
Acknowledgements the risk engine may require: similar_handle_acknowledged, recent_change_acknowledged, new_recipient_acknowledged, recipient_confirmed.
Idempotency-Keyheaderoptional
Strongly recommended (the SDK always sends one). Same key + same body returns the original payment. Idempotency.
201 Created (abridged)
{
  "object": "payment",
  "payment_id": "pay_q95qhew47n5fhqqrnekv",
  "status": "CREATED",
  "type": "DIRECT",
  "source": "wallet",
  "recipient": { "handle": "@lyra", "profile_id": "fp_2b4d6f8h0k2m4p6r8t0v", "display_name": "Lyra Okoye", "sandbox": false },
  "amount": "0.05",
  "amount_base_units": "50000000000000000",
  "asset": "ETH",
  "network": "robinhood",
  "chain_id": 4663,
  "memo": "Logo bounty",
  "destination": { "address_masked": "0x65D2…72a6", "verification_version": 1 },
  "receipt_url": "https://fomopay.family/payment/pay_q95qhew47n5fhqqrnekv",
  "checkout_url": "https://fomopay.family/payment/pay_q95qhew47n5fhqqrnekv",
  "sender_wallet": null,
  "idempotency_key": "bounty_logo_2026_10",
  "idempotent_replayed": false,
  "warnings": [],
  "risk": { "action": "ALLOW", "flags": [] },
  "created_at": "2026-10-12T19:12:04.874Z"
}
On-chain transfers are final

Once a transfer confirms it can't be reversed — not by you, not by FomoPay. A payment can be canceled only before it's submitted. Show the recipient's avatar, name and handle to whoever approves a payout.

Paying from a vault

TypeScript
const payment = await fp.payments.create({
  recipient: "@lyra",
  amount: "250",
  asset: "USDG",
  source: "vault",
  vaultId: "vault_3n5p7r9t1v3x5z7b9d1f",
  reason: "Contributor payout — October",
});

Identical to POST /v1/rewards. The payment is executed by FomoPay's payout signer with transferFrom(vault, recipient, amount); you can watch it move through SUBMITTED → CONFIRMING → CONFIRMED.

When the risk engine steps in

OutcomeWhat you get
Allowed201 with status: "CREATED".
Allowed, with warnings201 with warnings and risk.flags — a lookalike handle (SIMILAR_HANDLE), a first-time recipient. Show them in your own review step; people paying at the checkout are asked to acknowledge them before signing.
Needs a confirmation428 CONFIRMATION_REQUIRED with details.required — used for interactive senders. Retry with confirmations once a person has acknowledged.
Needs approval201 with status: "PENDING_APPROVAL" and approvals_required. A project Finance/Admin approves it in the dashboard.
Held201 with status: "HELD" and hold_until — e.g. the recipient changed their payout wallet recently (vault/API payouts wait out the cooldown).
BlockedAn error: PAYMENT_LIMIT_EXCEEDED, VAULT_INSUFFICIENT, COMPLIANCE_BLOCKED … see Errors.
warnings.ts
const p = await fp.payments.create({ recipient: "@Iyra", amount: "900", asset: "USDG" });

p.warnings; // [{ code: "SIMILAR_HANDLE", message: "Similar handle detected: @lyra (Lyra Okoye). Confirm recipient carefully." }, …]
p.risk;     // { action: "ALLOW", flags: [{ code: "SIMILAR_HANDLE", severity: "WARN", … }] }
// The payer sees the same warning on the checkout page and must acknowledge it before signing.

Lifecycle

StatusMeaning
CREATEDRecorded; recipient and destination snapshotted. Wallet payments wait at the checkout link.
PENDING_APPROVALOver a project approval threshold — waiting for approvers.
HELDPaused by a risk rule until hold_until (or manual review).
AWAITING_SIGNATUREThe exact transaction was prepared; waiting for the wallet to sign and send it.
SUBMITTEDBroadcast to Robinhood Chain; tx_hash is known.
CONFIRMINGMined, waiting for the required confirmations.
CONFIRMEDConfirmed on-chain and verified by FomoPay. Receipt number assigned; payment.confirmed sent.
FAILEDReverted, dropped/replaced, or failed verification. error says why in plain language.
CANCELEDCanceled before submission.

CONFIRMED is never optimistic. FomoPay waits for the chain's confirmations, then verifies the receipt succeeded, the sender is right (your vault's payout signer or the paying wallet), and — for USDG — that exactly one Transfer(from, recipient, amount) event matches the snapshot. ETH transfers must match recipient and value exactly.

Every payment is attributable on-chain. FomoPay appends a random 32-byte payment reference to the transfer calldata. ERC-20 decoding ignores the extra bytes (about 500 gas), and the reference ties the transaction to exactly one payment — a transaction hash can settle only one payment, ever.

The destination is frozen at creation. If @lyra changes her payout wallet after you create a payment, that payment still goes to the wallet verified when you created it (destination.verification_version). Nothing is silently redirected.

Retrieve a payment

GET/v1/payments/{payment_id}scope payments:read
const p = await fp.payments.get("pay_8k2m4q7r9t1v3x5z6b0c");
if (p.status === "CONFIRMED") console.log(p.tx_hash, p.receipt_number);

Reading an in-flight payment also checks the chain, so polling is never staler than the chain itself. Prefer webhooks in production; for scripts the SDK has fp.payments.wait(id).

The payment object
{
  "object": "payment",
  "payment_id": "pay_8k2m4q7r9t1v3x5z6b0c",
  "status": "CREATED",
  "type": "REWARD",
  "source": "vault",
  "recipient": {
    "handle": "@lyra",
    "profile_id": "fp_2b4d6f8h0k2m4p6r8t0v",
    "display_name": "Lyra Okoye",
    "sandbox": false
  },
  "amount": "500",
  "amount_base_units": "500000000",
  "asset": "USDG",
  "network": "robinhood",
  "chain_id": 4663,
  "memo": null,
  "reason": "FomoRun #42",
  "metadata": {},
  "tx_hash": null,
  "explorer_url": null,
  "block_number": null,
  "destination": { "address_masked": "0x65D2…72a6", "verification_version": 1 },
  "receipt_url": "https://fomopay.family/payment/pay_8k2m4q7r9t1v3x5z6b0c",
  "receipt_number": null,
  "checkout_url": null,
  "sender_wallet": "0x80DA3A104f069fABfC294923047971551B0c2725",
  "vault_id": "vault_3n5p7r9t1v3x5z7b9d1f",
  "bulk_batch_id": null,
  "visibility": "unlisted",
  "platform_fee_base_units": "0",
  "approvals_required": 0,
  "hold_until": null,
  "risk_flags": [],
  "error": null,
  "idempotency_key": "FOMORUN_42_WINNER",
  "idempotent_replayed": false,
  "warnings": [],
  "risk": { "action": "ALLOW", "flags": [] },
  "created_at": "2026-10-12T18:04:11.000Z",
  "submitted_at": null,
  "confirmed_at": null,
  "finalized_at": null
}

The payment object never contains the recipient's full wallet address — only destination.address_masked. explorer_url links the transaction on Robinhood Chain's explorer (it's null on the local development chain, which has none).

List payments

GET/v1/paymentsscope payments:read
Query
statusstringoptional
One or more, comma-separated: SUBMITTED,CONFIRMING.
recipientstringoptional
A handle: @lyra.
typeDIRECT | REWARD | BULKoptional
assetUSDG | ETHoptional
sourcewallet | vaultoptional
limit1–100optional
Page size.
cursorstringoptional
The previous page's next_cursor.
// one page
const page = await fp.payments.list({ status: ["SUBMITTED", "CONFIRMING"], limit: 50 });

// or everything, page by page
for await (const p of fp.payments.iterate({ recipient: "@lyra", asset: "USDG" })) {
  console.log(p.payment_id, p.status, p.amount);
}
200 OK
{
  "object": "list",
  "data": [ { "object": "payment", "payment_id": "pay_8k2m4q7r9t1v3x5z6b0c", "status": "CONFIRMING", … } ],
  "next_cursor": "eyJ0IjoiMjAyNi0xMC0xMlQxODowNDoxMS4wMDBaIn0",
  "has_more": true
}

A recipient's payments

GET/v1/profiles/@{handle}/paymentsscope payments:read

Only your project's payments to that identity — never anyone else's, and never lifetime earnings. Financial privacy is the default.

const history = await fp.profiles.payments("@lyra", { limit: 20 });
history.data.forEach((p) => console.log(p.created_at, p.amount, p.asset, p.status));