To match a crypto payment to the correct customer order, create the business order first, give it a stable internal ID, and create a separate payment invoice that stores that ID as merchant context. Save the payment provider’s invoice ID on the order. When a trusted server-side status or signed webhook reports payment, use the returned merchant reference to find the order, confirm the invoice reached the required state, record the token, network, amount and transaction hash, and make fulfilment idempotent. The reliable relationship is merchant order ID → payment invoice ID → confirmed blockchain transaction.

RecordWhat it provesWhat it cannot prove alone
Merchant orderWho owes what and what fulfilment meansThat funds arrived on-chain
Payment invoiceWhat token, route, amount and time window were requestedThat the merchant completed its own fulfilment
Blockchain transactionA transfer occurred with public on-chain fieldsWhich commercial order the merchant intended it to pay
Signed payment eventThe payment service connected its invoice to a status and transactionThat a downstream worker applied the update only once

A blockchain transfer does not contain your order

A public blockchain records asset movement. For an ERC-20 token, the standard Transfer event contains the sender, recipient and value, and the transaction receipt identifies the transaction. The ERC-20 specification does not include an ecommerce order number, customer account, product SKU or fulfilment rule. Those facts belong to the merchant system.

This distinction is the source of most matching problems. Seeing 100 USDC arrive at a wallet answers “did a token transfer occur?” It does not answer “was this ORDER-4821, a late payment for ORDER-4710, or an unrelated transfer?” A block explorer is a useful view of blockchain evidence, not an order database.

Build the connection before sending checkout to the customer. If the merchant waits until after funds arrive and then tries to infer the order from address, amount or time, every ambiguous case becomes manual support work.

Do not use address, amount or screenshots as the join key

A shared receiving address is not unique to an order. Two customers can pay the same wallet at nearly the same time. The same customer can reuse an older instruction. Several supported EVM networks can display the same address format even though transfers occur on different chains.

Amount alone is also unsafe. Two orders can legitimately have the same price, and a customer can underpay, overpay or send a rounded value. Deliberately generating strange decimal amounts may reduce collisions at small volume, but it still does not replace an explicit order-to-invoice relationship and an exception policy.

When the verified amount differs from the invoice, use the underpaid and overpaid invoice workflow and keep the order out of normal automatic fulfilment.

A timestamp is contextual evidence, not identity. Network congestion, exchange withdrawal batching and a late customer can move the transaction outside the expected window. A sender address is not a stable customer identifier either: an exchange may send withdrawals from infrastructure shared by many users, while one customer can use several wallets.

Screenshots and customer-supplied transaction hashes should start an investigation, not close an order. A real hash can describe the wrong token, recipient, network or amount, and the same hash can be presented for a second order. The manual crypto payment verification checklist explains how to validate the underlying transfer.

Use three records with explicit foreign keys

Treat the merchant order, payment invoice and blockchain transaction as separate records with different responsibilities.

FieldStore it inWhy it is needed
Local order IDMerchant database and payment contextStable business key for lookup and fulfilment
Payment invoice IDMerchant order and providerDirect retrieval and support reference
Expected business amountMerchant orderWhat the customer owes commercially
Payable token amountPayment invoiceExact amount shown in checkout
Token and networkPayment invoice and final payment recordPrevents unlike routes from being merged
Payment statusPayment record plus status historyControls the permitted next action
Transaction hashFinal payment recordLocates public blockchain evidence
Paid and expiry timestampsPayment recordExplains timing and exception decisions
Processed event or delivery keyWebhook inboxPrevents repeated side effects
Manual decision and reviewerException logMakes overrides reconstructable

Use an immutable database ID or stable UUID as the merchant order reference. Do not use a display label that staff can edit or reuse. Avoid putting secrets, private customer data or wallet keys in metadata: payment context can appear in logs, dashboards and webhook bodies.

The payment invoice ID should be stored on the merchant order immediately after creation, before redirecting the customer. This lets support navigate in both directions: from an order to its payment request and from a provider invoice back to the business record.

Create the payment invoice repeat-safely

A normal website flow is:

  1. Validate the cart or service request.
  2. Create the local order in a payment_pending state.
  3. Generate a stable idempotency key derived from that order and payment attempt.
  4. Create the payment invoice with the local order ID in merchant context.
  5. Save the returned invoice ID and checkout URL in the same database transaction or a recoverable workflow.
  6. Send the customer to hosted checkout.

Idempotency at invoice creation matters because network calls fail ambiguously. The provider may have created an invoice even when the merchant timed out before receiving the response. Retrying without the same key can create a second live payment request. Retrying with a stable key should return the original result or make the conflict explicit.

With GramPayBot, the merchant reference is passed as payload, and Idempotency-Key prevents duplicate invoices after a timeout. An identical replay returns the original invoice; reusing the key with different parameters returns a conflict. Save the returned public_id and use web_app_invoice_url for browser checkout. The exact request and response rules are in the API quickstart and API reference.

Do not assume one business order can have only one payment attempt forever. A previous invoice may expire, the amount may change or the buyer may request another route. Model attempts explicitly:

order → payment attempt 1 (expired) → payment attempt 2 (paid)

Only one attempt should be allowed to close the order automatically. Keep old attempts for audit and late-payment review instead of overwriting them.

Let the server own the final order transition

The browser is not an authoritative payment channel. A customer may close the page before confirmation or reload an old success state after an order was refunded or cancelled. Client-side code must not grant access simply because the URL contains a success parameter.

Use an authenticated API lookup or a signed server-to-server webhook. For a GramPayBot invoice event, payload.payload contains the merchant reference, while the invoice object supplies public_id, status, token, paid network and tx_hash. The event and payload documentation defines the exact envelope.

A safe webhook handler follows this order:

  1. Read the exact raw request body.
  2. Verify its HMAC signature in constant time.
  3. Persist the delivery and body before starting slow business work.
  4. Deduplicate by a stable delivery or event key.
  5. Acknowledge the valid event quickly.
  6. In an asynchronous worker, find the payment attempt and order using stored IDs.
  7. Apply only an allowed state transition.
  8. Commit the payment record and fulfilment marker atomically where possible.

GramPayBot can retry real events up to 17 times over roughly four days, so duplicate delivery is an expected reliability mechanism. This is not unique to crypto payments: Stripe’s official webhook best practices also tell integrations to record processed event IDs and ignore duplicates.

Make fulfilment idempotent, not just webhook receipt

Deduplicating a delivery ID is necessary but not sufficient. Two different events or a webhook and a polling worker can observe the same paid invoice. The business action itself must be protected by a unique constraint or conditional state transition.

For example, the worker can update an order only when its current state is payment_pending and write a unique fulfilment record keyed by the order ID. If zero rows changed, another process already handled the transition and the worker exits without shipping or crediting again.

BEGIN
  lock order ORDER-4821
  if order.payment_state != payment_pending: stop safely
  record paid invoice and tx_hash
  set order.payment_state = paid
  create unique fulfilment job for ORDER-4821
COMMIT

The goal is not exactly-once webhook delivery; networks cannot promise that end to end. The practical goal is at-least-once delivery with exactly-once business effect.

Map payment states to permitted business actions

Do not let several teams invent different meanings for the same status.

Payment stateMerchant interpretationAutomatic action
activeValid request, accepted payment not yet confirmedKeep order pending
paidTracked invoice met the provider’s confirmation rulePermit the defined idempotent fulfilment
expiredPayment window ended without an accepted matchBlock normal fulfilment; inspect late transfers separately
cancelledRequest intentionally closedDo not accept it as the current payment attempt

Payment state and order state do not need identical names. A paid invoice might move an order to ready_for_review when the product requires compliance or inventory checks. Likewise, an expired invoice does not necessarily cancel the commercial order; the website can offer a new payment attempt.

Store status history instead of only the latest label. Support may need to know whether an invoice expired before a transfer arrived, who approved a manual override and which event triggered fulfilment.

Route mismatches into explicit exception states

The happy path should be narrow. Everything else needs evidence and an owner.

  • Wrong token or contract: do not treat a ticker match as a valid payment.
  • Wrong network: funds may exist at an address on another chain without matching the requested route.
  • Underpayment or overpayment: preserve expected and received amounts; apply written business policy.
  • Late payment: link it to the historical attempt, but do not silently reopen a changed order.
  • Duplicate transaction hash: reject it as proof for another invoice or order.
  • Paid invoice with missing local order: quarantine the event and alert; do not drop it.
  • Order already fulfilled: acknowledge the retry and avoid a second side effect.
  • Manual payment override: record who made it, why and what external evidence was used.

Never erase the actual transaction to make records fit the desired outcome. Keep the exception linked to the payment attempt and let an authorized person accept, refund or request a difference according to policy. In a direct-to-wallet model, the merchant controls outgoing refunds; the monitoring service cannot reverse an on-chain transfer.

Reconcile operations, blockchain evidence and accounting

Operational matching determines whether the website may fulfil an order. Accounting reconciliation is a related but broader task. A finance record may also need the fiat value at the required recognition time, fees, conversion, wallet movement, customer document and refund history. A blockchain transaction is not automatically a tax invoice, contract or complete ledger entry.

For daily operations, query paid payment attempts and compare them with paid merchant orders. Flag records that exist on only one side, duplicate transaction hashes and orders whose fulfilment state disagrees with payment state. Periodic reconciliation catches problems caused by disabled webhooks, deployment failures or manual changes.

Use the transaction hash to locate evidence, but keep the local order ID and payment invoice ID as the business join. Ethereum’s documentation explains that token transfers are exposed through searchable smart-contract event logs; those logs provide on-chain facts, not the merchant’s commercial meaning.

A complete GramPayBot example

Suppose a customer selects a 30-day Pro plan:

  1. The SaaS creates ORDER-4821 with state payment_pending.
  2. Its backend calls createInvoice with amount 99.00, payload: "ORDER-4821" and Idempotency-Key: ORDER-4821:crypto:1.
  3. GramPayBot returns a public_id and hosted checkout URL; the SaaS stores both before redirecting.
  4. The customer pays the displayed USDC amount on Base.
  5. GramPayBot confirms the matching transaction and sends a signed invoice_paid event containing the order reference, invoice status, paid token, network and transaction hash.
  6. The webhook endpoint verifies the raw-body signature, stores the delivery and returns 2xx.
  7. A worker locks ORDER-4821, confirms it is still pending, records the payment and creates one unique activation job.
  8. Any repeated delivery is acknowledged but cannot activate the plan again.

This flow keeps responsibilities clear. GramPayBot provides invoice checkout, supported-network monitoring and the order-linked payment result. The SaaS remains authoritative for the product, customer, access period and fulfilment. The automated payment verification guide explains the wider detection process, and the website use case shows where it belongs in an online sales flow.

Test the failure paths before launch

Run a low-value production pilot and verify more than the successful payment. Repeat the invoice-creation request with the same idempotency key. Deliver the same valid webhook twice. Let one invoice expire. Send an incorrect amount without fulfilling. Simulate a worker crash after persisting the event but before changing the order. Confirm that reconciliation can find and safely replay unfinished work.

The integration is ready when every paid order can be traced from its commercial obligation to one payment invoice and one accepted transaction, while every retry and mismatch produces either no duplicate side effect or a visible review item.

Next step

Automate payment verification on your website

Create an invoice for each order, let the buyer pay through hosted checkout and receive an order-linked result.

Explore website payments →