No Fianto fee during the beta. Any fee comes with at least 30 days notice. See pricing
Payments7 min read

How a merchant knows a crypto payment arrived.

To know a crypto payment really arrived, you need three answers: which transfer belongs to which order, whether it moved the right token and amount into your wallet, and whether the network has made it final. On Solana the first is solved with a reference key added to the transfer, the second by reading the transfer itself, and the third by waiting for the finalized commitment level. Fianto does all three and sends your server a signed order.paid webhook only once the payment is finalized.

On this page
What does it take to confirm a crypto payment?How do you match a crypto payment to an order?How do you check the token and amount of a USDC payment?When is a crypto payment on Solana safe to fulfil?How does Fianto tell your server that a payment arrived?What if the money arrived but the order isn't paid?Can you confirm crypto payments yourself without a gateway?SourcesQuestions
TL;DR
  • Match by reference key, not by amount or memo: Fianto puts one on every session's price transfer and sweeps for it every 60 seconds.
  • Check the token mint, your destination and the amount in base units: 25 USDC is "25000000" with USDC's 6 decimals.
  • Fulfil only at finalized. Fianto marks the order PAID and sends order.paid then, trying each delivery up to 8 times over about 28 hours.

What does it take to confirm a crypto payment?

Confirming a crypto payment takes three separate checks, and skipping any one of them is how merchants ship goods that were never paid for. A screenshot from the customer proves none of them. A wallet notification proves only that something arrived.

  1. Match: find the one transfer that pays this order, among every transfer your wallet receives.
  2. Check: confirm it moved the right token, to your account, for the right amount.
  3. Wait: act only once the block holding it can no longer be rolled back.

Each check has a cheap way that breaks and a reliable way that takes a little setup.

How do you match a crypto payment to an order?

The reliable way to match a payment on Solana is a reference key: a unique address attached to the transfer that you can search for later. The two shortcuts both fail under load.

By amount

Matching by amount works until two customers buy the same thing. Adding odd cents to each order (25.0013, 25.0021) helps, but a customer who rounds, or pays from an exchange that deducts a withdrawal fee, breaks the match.

By memo

A memo is a short text note stored in the transaction. The Solana Pay specification defines it as a string that "must be included in an SPL Memo instruction in the payment transaction." It works when your software builds the transaction. When customers type it themselves, some forget it, mistype it, or use a wallet without a memo field, and anyone reading the chain can read it.

By reference key

The same spec says reference values go in "as read-only, non-signer keys" on the transfer instruction, and that "the getSignaturesForAddress RPC method can be used locate transactions this way." Because the key is unique per order, a lookup by that address returns exactly the transactions for that order.

Fianto gives every checkout session its own reference and appends it to the transfer of the price to your wallet. Every 60 seconds it looks up transfers carrying the reference of each open session, and keeps looking for 24 hours after a session closes. A payment found that way, even one Fianto did not broadcast itself, starts at CONFIRMED and goes through the same checks as the rest.

How do you check the token and amount of a USDC payment?

To check a USDC payment, read three fields from the transfer: the token mint, the destination account and the amount. A mint is the on-chain record that defines a token. Circle lists EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v as the mainnet address of USDC on Solana in its USDC contract addresses (checked 15 October 2026); a token with the same name and another mint is not USDC.

The destination is your USDC token account, not your wallet address itself. Solana's token docs explain that "a Token Account tracks individual ownership of tokens for a specific mint account for a specific owner." Amounts are integers in base units: USDC has 6 decimals, so 25 USDC is 25000000.

Fianto does this check before any money moves. It builds the transaction the customer signs, then compares the signed version with what it built, instruction by instruction: mint, decimals, source, destination, amount and reference. A transaction that differs is not broadcast. A transfer that carries a session's reference but does not match its terms, sent some other way, is recorded on the order as an unmatched transfer, up to 20 per session, and never counted as the payment.

When is a crypto payment on Solana safe to fulfil?

A Solana payment is safe to fulfil at the finalized commitment level, not at confirmed. The Solana RPC documentation defines confirmed as "a block directly voted on by a supermajority of stake, meaning more than two-thirds of the network's active stake," and finalized as "a block the cluster recognizes as finalized with maximum lockout". Its third level, processed, "can still be rolled back."

Fianto uses both moments for different people. The customer's checkout page shows the receipt at confirmed, so they are not left waiting. The order turns PAID, the payment SUCCEEDED, and the webhooks go out only at finalized. Why the two levels differ is explained in confirmed vs finalized on Solana.

How does Fianto tell your server that a payment arrived?

Fianto tells your server with a signed order.paid webhook, sent once all three checks have passed. The body carries your own order_id, the amounts in base units and the Solana transaction signature, so you can look the transfer up on any explorer, as reading a Solana payment on an explorer shows. Shortened from the event reference, for a 25 USDC order with no Fianto fee, as during the beta:

order.paid
{
  "id": "evt_25eaf6aa00802f4e758d9dbfb332941a",
  "type": "order.paid",
  "data": {
    "order_id": "order_1001",
    "status": "PAID",
    "amount": "25000000",
    "fee_amount": "0",
    "total_amount": "25000000",
    "currency": "USDC",
    "payment": { "id": "fian_pay_5caF...", "signature": "dbmKAFCA..." },
    "paid_at": "2026-09-28T10:02:14.000Z"
  }
}
  • Each request is signed the Standard Webhooks way, with webhook-id, webhook-timestamp and webhook-signature headers and a whsec_ secret. Verify before you trust it.
  • Delivery is at least once and unordered, so store event.id with the fulfilment and skip repeats.
  • Any 2xx within 10 seconds counts. Otherwise Fianto tries each delivery up to 8 times over about 28 hours; after 5 exhausted deliveries in a row the endpoint is suspended and deliveries are held.
  • Events are kept 90 days and can be listed with GET /v1/events if your server was down.

What to grant and revoke on each event is set out in webhooks for access, and keys and routes on the developers page. A subscription creates no order, so order.paid never fires for one: subscriptions send subscription.created and subscription.renewed instead.

Fianto matches, checks and waits for finality, then signs one webhook. The merchant pays nothing.

See the hosted checkout

What if the money arrived but the order isn't paid?

Money can reach your wallet without a paid order in three situations, and Fianto records each one:

SituationWhat Fianto recordsWhat you see
A payment finalizes after its session expiredThe order still turns PAID, unless you opened a newer session for itorder.paid, without checkout.session.completed
A second payment for an order already paidAn unmatched transfer on the orderorder.duplicate_payment and a row on Needs attention
A transfer with the wrong amount or feeAn unmatched transfer, or a FAILED payment whose USDC landedA row under "Orders with an unmatched transfer" or "Failed payments where money moved"

In the last two, the customer's USDC is in your wallet and Fianto cannot pull it back: there is no refund API, so a refund is a transfer you send yourself. Fianto cannot see that transfer either, so the row stays listed after you refund; keep your own note. The Needs attention docs describe the three tables, and chargebacks vs crypto refunds covers the refund itself.

Can you confirm crypto payments yourself without a gateway?

You can confirm crypto payments yourself with a Solana RPC endpoint and some code, and at a few payments a day it is a reasonable project. The loop looks like this:

  1. Generate a fresh reference key per order and put it on the transfer you ask the customer to sign.
  2. Poll getSignaturesForAddress for that key until a signature appears or the order expires.
  3. Fetch the transaction at the finalized commitment and check its mint, your token account and the amount.
  4. Store the signature against the order, so one transfer can never pay two orders, and flag any second transfer for the same reference.

The work is in the edges: transfers that land after you gave up, an RPC provider that misses a poll, customers who pay twice. A crypto payment gateway handles those for you, and with Fianto the merchant pays nothing for it. If you would still rather build, start with the hosted checkout vs a bare wallet address comparison to see which parts you are taking on.

Sources
Questions

Questions about confirming crypto payments.

Is a transaction hash proof of payment?

Only after you look it up yourself and check the token, the destination and the amount, at finalized. A hash pasted by a customer can belong to a different or failed transaction.

What is a Solana Pay reference?

A unique address added as a read-only key to the transfer instruction. Searching the chain for that address finds the transactions for exactly one order.

How long until Fianto sends order.paid?

As soon as the payment is finalized on Solana, after the customer has already seen "Payment received" at confirmed. Fianto sends nothing at confirmed.

Can I check a payment without webhooks?

Yes. Read the order from your server, for example by your own order_id with the SDK, and fulfil only when its status is PAID.

What if a customer pays the same order twice?

The second transfer is recorded on the order as unmatched, Fianto sends order.duplicate_payment, and the order appears on Needs attention. The USDC is in your wallet, so any refund is yours to send.

Read next.

Payments8 min read

What a hosted USDC checkout does that a wallet address doesn't

Posting your wallet address gets you paid, but it tells you nothing else: which order the USDC was for, whether the amount was right, or when the transfer became final. A hosted USDC checkout wraps each payment in a session that carries your order id, writes the exact amount into the transaction the customer signs, expires on a timer, and tells your server only once Solana has finalized the payment. For a tip or a one-off payment from someone you know, an address is fine; for orders you fulfil automatically, use a session.

Payments8 min read

What is a crypto payment gateway, and how does a Solana one work?

A crypto payment gateway is the software between a customer's wallet and your order system: it tells the customer exactly what to pay, recognises the payment on the blockchain, decides when it is final, and tells your server. The classic design watches an address for incoming coins, while on Solana a gateway can build the exact transaction for the customer to sign, which rules out underpayments and wrong-token mistakes. Fianto is a gateway of the second kind, for USDC on Solana, and it never holds the money.

Solana9 min read

Solana confirmed vs finalized: when a payment is final, and what Fianto waits for

A Solana payment is confirmed once validators holding more than two-thirds of the stake have voted on its block, usually about one slot after it lands, and finalized once that block can no longer be rolled back, which takes about 12.8 seconds until the Alpenglow upgrade activates (scheduled for 21 October 2026). Fianto shows the customer a receipt at confirmed, but marks the order PAID and sends order.paid only at finalized. Once Alpenglow is live, solana.com expects finality in roughly 150 milliseconds, and confirmed and finalized become the same state.

Fulfil on finalized, never on a screenshot.

Verify the webhook, store the event id, then ship.

New posts are announced on X and Discord.