Skip to main content

Public payment endpoints

These endpoints power the hosted checkout — and, for API checkout mode, your own embedded payment page. They need no API key. For hosted payments, knowledge of the payment UUID is the credential (which is why payment IDs are unguessable UUIDs and cross-gateway probing always 404s). In hosted mode you normally never call them yourself; in API mode your server calls the select endpoint (and optionally expire) directly, and polls status with statusOnly=true. The sandbox simulate endpoint is also useful in automated tests.

API-mode payments are IP-gated end to end

For a payment created with checkoutMode: "API", every endpoint on this page — the read, expire, select, and sandbox simulate — requires the caller's source IP to be on the gateway's allowlist (403 forbidden otherwise). API-mode payments are only reachable from whitelisted server IPs — the payment UUID alone reveals nothing. Hosted payments are unaffected.

GET /public/payments/{id}

The redacted payment projection the checkout renders from. The response is mode-aware: hosted payments always get the full payment object; API-mode payments are source-IP gated and can opt into a slim status object via statusOnly — without it, the full payment object is returned whatever the status.

curl https://api-staging.finomesh.com/api/v1/public/payments/0d9f3a64-…
{
"success": true,
"data": {
"id": "0d9f3a64-4f0c-4b6e-9f6e-2f4f4c1b8a21",
"gatewayId": "9b1de2c8-7a41-4c8e-b3f2-6d5e4a3b2c1d",
"currencyId": "3f8a6c1e-2b4d-4e6f-8a9b-1c2d3e4f5a6b",
"currencyCode": "USD",
"amount": "49.99",
"amountUsd": "49.99",
"status": "PENDING",
"type": "DIRECT",
"checkoutMode": "API",
"isSandbox": false,
"timeoutSeconds": 3600,
"expiresAt": "2026-07-03T12:10:00Z",
"txFeePayer": "CUSTOMER",
"commissionFeePayer": "CUSTOMER",
"overAcceptableDiff": "0.5",
"underAcceptableDiff": "0.5",
"acceptableDiffKind": "PERCENTAGE",
"checkAmountDeviation": true,
"offers": [
{
"id": "7c2e1f30-5d6a-4b8c-9e0f-1a2b3c4d5e6f",
"assetId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"assetSymbol": "USDT",
"assetNetwork": "TRON",
"assetType": "TRC20",
"assetLogoUrl": "https://files.finomesh.com/assets/usdt.png",
"networkLogoUrl": "https://files.finomesh.com/networks/tron.png",
"assetPriceUsd": "1.00",
"amount": "49.99",
"txFee": "1.20",
"commissionFee": "0.50",
"totalValue": "51.69",
"networkFeeNative": "13.6",
"networkFeeUsd": "1.20",
"feeNativeSymbol": "TRX"
}
],
"createdAt": "2026-07-03T10:21:43Z",
"updatedAt": "2026-07-03T10:21:43Z"
}
}

It contains the amounts, status, checkoutMode, offers, and — once the payment resolves on-chain — the resolving txHash, receivedAmount, and a ready-to-use explorerTxUrl block-explorer link. It deliberately excludes merchant-only fields: webhookUrl, webhookParams, webhookDispatched, and internal identifiers. (callbackUrl and callbackParams are present for hosted payments — the callback is a browser redirect, so its target is payer-facing by definition.)

Nothing on this surface lets a payer affect what you get paid: it is read-only state plus the narrowly-scoped POSTs below.

Query parameters:

ParameterTypeRequiredDefaultDescriptionExample
statusOnlybooleanNofalseAPI mode only — ignored for hosted payments. When true, returns the slim status object instead of the full projection — the recommended cheap status-inquiry poll. A non-boolean value returns 422 validation_error.true
Hosted-internal fields

The tables below document the merchant-facing contract. Responses for hosted payments may additionally carry presentation fields consumed only by the hosted checkout UI (theming, payer-inquiry flags, per-offer explorer templates); they are not part of the merchant contract and are omitted entirely for checkoutMode: "API" payments.

API-mode behavior

For payments created with checkoutMode: "API" this endpoint differs in two ways:

  • Source-IP gated. The same gate as the select endpoint: the caller's IP must be on the owning gateway's allowlist, else 403 forbidden. An API-mode payment is completely invisible to anyone but your whitelisted servers — even with the UUID.
  • statusOnly opt-in. With ?statusOnly=true you get the slim status object — the recommended cheap status-inquiry poll. Without it, the full payment object is returned whatever the status (minus the redacted fields above), so you can re-fetch everything about the payment at any point in its life.

The recommended pattern: poll with ?statusOnly=true as your internal status inquiry — a tiny, stable payload — and fulfil only from verify, the authoritative read.

curl "https://api-staging.finomesh.com/api/v1/public/payments/{paymentId}?statusOnly=true"

Slim status object

{
"success": true,
"data": {
"paymentUid": "0d9f3a64-4f0c-4b6e-9f6e-2f4f4c1b8a21",
"status": "SUCCESS",
"isVerified": false,
"verifiedAt": null
}
}
FieldTypeNullableDescriptionExample
paymentUidstring (UUID)NoThe payment UUID.0d9f3a64-…
statusstring (enum)NoOne of PENDING, SUCCESS, ACCEPTABLE, MISMATCH, EXPIRED.SUCCESS
isVerifiedbooleanNoWhether you have already called verify on this payment. Reading it here never flips it — only verify does.false
verifiedAtstring (RFC 3339)YesWhen the payment was first verified. null until then.null

Errors

HTTP statusError codeMeaningHow to fix
403forbiddenAPI-mode payment and the caller's source IP is not on the gateway's allowlist.Whitelist the observed source IP echoed in the message — behind a CDN, NAT, or egress proxy it can differ from your server's DNS-advertised IP.
404not_foundUnknown payment.Check the payment UUID.
422validation_errorstatusOnly is not a valid boolean.Send statusOnly=true or statusOnly=false.

Payment object fields

The object returned by this endpoint — and inlined as payment on an API-mode create.

Monetary values are decimal strings; example values are shown in the JSON sample above.

FieldTypeNullableDescription
idstring (UUID)NoThe payment UUID.
gatewayIdstring (UUID)NoThe owning gateway's ID.
currencyIdstring (UUID)NoInternal ID of the invoice currency.
currencyCodestringYesInvoice currency ISO code (e.g. USD). Omitted if unresolved.
amountstring (decimal)NoInvoice amount in currencyCode units. Decimal string — never a number, to preserve precision.
amountUsdstring (decimal)NoInvoice amount converted to USD. Decimal string — never a number.
openAmountbooleanNotrue for an open-amount payment — the payer chooses what to send. When true, each offer's amount / totalValue are 0 placeholders and must not be shown as a figure to send; the checkout invites any amount instead. Always false unless your gateway is provisioned for open-amount.
minAmountUsdstring (decimal)YesPresent only when openAmount is true: the minimum USD value the first deposit must reach for the payment to resolve SUCCESS. The first deposit settles the payment either way — below the minimum it resolves MISMATCH (the minimum is the expected amount; the funds are still credited) — so show this figure to the payer before they send.
statusstring (enum)NoOne of PENDING, SUCCESS, ACCEPTABLE, MISMATCH, EXPIRED.
typestring (enum)NoDIRECT or PAYLINK.
checkoutModestring (enum)NoHOSTED or API.
isSandboxbooleanNotrue for a sandbox payment.
timeoutSecondsintegerNoValidity window in seconds. Prefer expiresAt for countdowns; this is the raw window length.
expiresAtstring (RFC 3339)NoThe payment's authoritative deadline. Drive countdowns from this server-owned value rather than re-deriving createdAt + timeoutSeconds client-side.
txFeePayerstring (enum)NoWho bears the network fee: MERCHANT or CUSTOMER.
commissionFeePayerstring (enum)NoWho bears the Finomesh commission: MERCHANT or CUSTOMER.
overAcceptableDiffstring (decimal)NoOverpayment tolerance. Decimal string — never a number. Read per acceptableDiffKind.
underAcceptableDiffstring (decimal)NoUnderpayment tolerance. Decimal string — never a number.
acceptableDiffKindstring (enum)NoHow the tolerances are read: ABSOLUTE or PERCENTAGE.
checkAmountDeviationbooleanNoWhether the acceptance band is applied at all.
callbackUrlstringYesBrowser redirect target. Present for HOSTED payments only.
callbackParamsobjectYesArbitrary merchant JSON echoed into the callback redirect.
txHashstringYesThe resolving on-chain transaction hash. Present once the payment resolves.
receivedAmountstring (decimal)YesCrypto amount actually received. Decimal string. Present once resolved.
paidOfferIdstring (UUID)YesWhich offer was paid. Present once resolved.
explorerTxUrlstringYesReady-to-use block-explorer link for txHash — the paid offer's chain-explorer URL with the transaction hash already substituted. Present once the payment has an on-chain transaction (statuses SUCCESS, ACCEPTABLE, MISMATCH) and the chain has an explorer configured. Sandbox payments never have one (no real transaction exists).
languagestringYesCheckout display-language hint.
offersarray<Offer>NoThe payable assets — see the offer object sub-table.
createdAtstring (RFC 3339)NoCreation timestamp.
updatedAtstring (RFC 3339)NoLast-update timestamp.

Offer object — offers[] element

One payable asset. The same shape is returned as offer by the select endpoint.

FieldTypeNullableDescription
idstring (UUID)NoThe offer ID — pass it to the select endpoint.
assetIdstring (UUID)NoThe underlying asset ID.
assetSymbolstringYesAsset ticker (e.g. USDT). Empty if metadata is unavailable.
assetNetworkstringYesChain the asset settles on (e.g. TRON, ETH, BSC).
assetTypestring (enum)YesToken standard: NATIVE, ERC20, TRC20, or BEP20.
assetLogoUrlstringYesAbsolute CDN URL of the asset icon (unsigned, stable).
networkLogoUrlstringYesAbsolute CDN URL of the network icon.
depositAddressstringYesThe address to pay. Populated only after the payer/merchant selects this offer (or another on the same network).
assetPriceUsdstring (decimal)NoUSD price the asset was quoted at. Decimal string — never a number.
amountstring (decimal)NoThe invoice principal expressed in the asset (fees excluded). Decimal string.
txFeestring (decimal)NoNetwork-fee portion the customer pays ("0" if the merchant bears it). Decimal string.
commissionFeestring (decimal)NoCommission portion the customer pays ("0" if the merchant bears it). Decimal string.
totalValuestring (decimal)NoThe exact amount the customer must send — principal plus any customer-paid fees folded in. Decimal string. Display this one.
networkFeeNativestring (decimal)YesEstimated network fee in the chain's native coin. Decimal string. Omitted when the estimate is stale.
networkFeeUsdstring (decimal)YesEstimated network fee in USD. Decimal string. Omitted when the estimate is stale.
feeNativeSymbolstringYesNative-coin ticker for networkFeeNative (e.g. TRX, ETH).

POST /public/payments/{id}/expire

Called by the checkout when its countdown reaches zero (in API mode, your server may call it when your countdown reaches zero). Idempotent and safe-by-construction: it transitions PENDING → EXPIRED only when the payment is genuinely past its deadline — calling it on a still-valid or already-paid payment is a no-op that returns the current state. A hostile caller cannot kill a valid payment.

For API-mode payments the call is source-IP gated like the read and select — an off-allowlist caller gets 403 forbidden before any effect. The response is the same full (redacted) payment object the GET serves, now showing the current status (EXPIRED if the expiry fired). Hosted expire is unchanged.

POST /public/payments/{id}/offers/{offerId}/select

Selects an offer and returns the deposit address and exact amount to pay — the heart of API checkout mode. Hosted payments never use it (the hosted checkout has its own channel); it exists so an embedded merchant's server can drive asset selection over REST.

curl -X POST https://api-staging.finomesh.com/api/v1/public/payments/{paymentId}/offers/{offerId}/select

Path parameters (there is no request body):

ParameterTypeRequiredDescriptionExample
idstring (UUID)YesThe payment UUID ({paymentId} in the path).0d9f3a64-…
offerIdstring (UUID)YesThe offer to select — an id from the payment's offers[].7c2e1f30-…

No request body and no auth header — but two server-side gates make this stricter than the rest of the public group:

  • the payment must have been created with checkoutMode: "API" and still be PENDING (409 otherwise);
  • the caller's source IP must be on the owning gateway's IP allowlist, configured in the dashboard (403 otherwise) — so the practical caller is the merchant's server, never a payer's browser.

200 OK — address assigned

{
"success": true,
"data": {
"assignmentStatus": "ASSIGNED",
"depositAddress": "TX7k2…",
"amount": "51.69",
"paymentStatus": "PENDING",
"expiresAt": "2026-07-03T11:21:43Z",
"offer": { "id": "…", "assetSymbol": "USDT", "assetNetwork": "TRON", "totalValue": "51.69", "…": "…" }
}
}

The 200 and 202 bodies share one shape — SelectOfferResult:

FieldTypeNullableDescriptionExample
assignmentStatusstring (enum)NoASSIGNED (address ready — 200) or PENDING (still deriving — 202, re-call after retryAfterMs).ASSIGNED
depositAddressstringYesWhere the customer must send the funds. Present when ASSIGNED; absent while PENDING.TX7k2…
retryAfterMsintegerYesPresent on PENDING (202) only — re-call the same endpoint after this many milliseconds.400
amountstring (decimal)NoThe selected offer's totalValue — the exact amount to send (customer-paid fees folded in). Decimal string — never a number.51.69
paymentStatusstring (enum)NoThe payment's current status — one of PENDING, SUCCESS, ACCEPTABLE, MISMATCH, EXPIRED (PENDING here).PENDING
expiresAtstring (RFC 3339)NoDeadline — do not display the address past it.2026-07-03T11:21:43Z
offerobjectNoThe full selected offer object (symbol, network, logos, fee breakdown) — enough to render the pay screen from this one response.(see offer table)

202 Accepted — assignment still in flight

Address derivation happens on demand and can take a moment. On 202 the body carries assignmentStatus: "PENDING" and a retryAfterMs hint — re-call the same endpoint after that delay; the call is idempotent and re-calling is the designed recovery. The address also appears on GET /public/payments/{id} in the offer's depositAddress once assigned. A copy-pasteable retry loop lives in the embedded guide.

Re-selecting

Selecting the same offer again — or a different offer on the same network — returns the same address. Selecting an offer on a different network derives a second address for that network: one address per network, shared by all of its offers.

Errors

HTTP statusError codeMeaningHow to fix
403forbiddenSource IP not on the gateway's allowlist.Whitelist your server's observed source IP (echoed in the message).
404not_foundUnknown payment, or the offer is not on this payment.Check the payment UUID and that offerId is one of this payment's offers[].
409conflictPayment is not API-mode, is no longer PENDING, or has expired.Only select on a PENDING payment created with checkoutMode: "API".

POST /public/payments/{id}/sandbox/simulate

Drives a sandbox payment into a terminal state. Rejected for non-sandbox payments.

For API-mode sandbox payments the call is source-IP gated like select (403 forbidden off-allowlist); hosted sandbox payments keep the browser-driven simulate buttons on the hosted checkout.

curl -X POST https://api-staging.finomesh.com/api/v1/public/payments/{paymentId}/sandbox/simulate \
-H "Content-Type: application/json" \
-d '{ "outcome": "acceptable", "offerId": "{offerId}" }'
FieldTypeRequiredDefaultDescriptionExample
outcomestring (enum)YesWhich terminal state to drive to: success, acceptable, mismatch, or expire."acceptable"
offerIdstring (UUID)NoWhich offer was "paid" — lets the simulator synthesize a realistic received amount for the three paid outcomes. Unused for expire."7c2e1f30-…"

Returns the updated public payment projection. All downstream effects (callback, webhook, verify) fire exactly as in production.