Crypto Payment Webhooks: Events, Security and Reliable Order Fulfilment

The webhook is the moment your store learns that a crypto payment is final. Here is how to design a handler that is secure, idempotent and boringly reliable, with an event map and a production checklist.

Updated: 4 min read
Crypto payment webhooks — signed invoice events flowing from a payment gateway to a merchant server

Crypto payment webhooks are HTTP requests a payment gateway sends to your server whenever an invoice changes status: the transfer is detected, confirmed on-chain, paid short, overpaid or expired. Your handler verifies the signature, updates the order and triggers fulfilment. Because crypto payments arrive asynchronously, minutes after the customer leaves your page, the webhook, not the browser, is the source of truth.

This guide focuses on the webhook side of an integration: the events to expect, how to verify them, how to make processing safe when the same event arrives twice, and how to test and monitor it. For creating invoices, see our crypto payment API integration guide.

Why webhooks instead of polling or redirects?

  • Redirects are not proof — a customer returning to your "thank you" page tells you nothing about whether money arrived.
  • Polling is wasteful — asking the API every few seconds for every open invoice does not scale and still lags.
  • Webhooks are push — the gateway tells you the moment something changes, with a signature you can verify.
Key takeaway: never mark an order as paid because of a redirect, a chat message or a screenshot. Only a verified webhook (or an authenticated API status check) counts.

The events you will receive

EventMeaningWhat your handler should do
invoice.paidTransfer detected, confirmations pendingShow "payment received, confirming"; do not deliver yet
invoice.confirmedRequired confirmations reachedMark paid and fulfil the order
invoice.underpaidLess than expected, below your toleranceTell the customer how much is missing; wait for a top-up
invoice.overpaidMore than expected arrivedFulfil; decide how to handle the difference
invoice.expiredNo valid payment within the time windowRelease reserved stock; keep the order unpaid

These are the event names mistKET uses. Other gateways use similar statuses with different names, so map them to your own order states in one place.

Verifying the signature

Anyone can send a POST request to a public URL, so every webhook must be authenticated. mistKET signs each delivery with HMAC-SHA256 and sends these headers:

  • X-Mistket-Event — the event name.
  • X-Mistket-Timestamp — Unix time included in the signature.
  • X-Mistket-Signature — sha256= followed by the HMAC of {timestamp}.{raw body} with your webhook secret.
  • X-Mistket-Delivery — a delivery ID for logging.
  1. Read the raw body — before any JSON parsing, because re-encoding changes the bytes.
  2. Recompute the HMAC — over the timestamp, a dot and the raw body, with your webhook secret.
  3. Compare in constant time — use hash_equals, hmac.compare_digest or crypto.timingSafeEqual.
  4. Reject stale timestamps — for example anything older than five minutes, to block replays.
  5. Only then parse and act — and return a 2xx response quickly.

The construction follows the standard HMAC pattern; keep the secret on the server and rotate it if it ever leaks.

Choosing and protecting the callback URL

Use a dedicated HTTPS path such as /webhooks/mistket, separate from your normal pages, and pass it as the callback URL when you create each invoice. Exclude it from CSRF protection, caching and redirects; a 301 redirect or a cached response is a common reason webhooks silently fail. Do not require a session cookie, since the gateway is not a logged-in user; the HMAC signature is the authentication. Finally, log every delivery with its event, order ID and response code so you can answer \"did we get the webhook?\" in seconds.

Idempotency: the same event can arrive twice

If your endpoint times out or returns an error, the gateway retries with back-off for hours. Network glitches can also deliver an event you already processed. Your handler must therefore be idempotent:

  • Key on the order ID — look up the order your invoice references.
  • Check the current state — if the order is already fulfilled, acknowledge and do nothing.
  • Use a transaction or lock — so two parallel deliveries cannot both ship the product.
  • Store the delivery ID — useful for debugging duplicates.
Tip: respond with 200 as soon as the event is verified and stored, then do slow work such as sending emails or provisioning servers in a background job. Slow handlers cause timeouts, and timeouts cause retries.

Designing order states around events

A simple state machine prevents most bugs: pending → confirming (on paid) → paid (on confirmed) → fulfilled, with side branches for underpaid and expired. Allow only forward transitions. An invoice.paid that arrives after invoice.confirmed, for example, should never move an order backwards. Late payments to expired invoices should create a record for review rather than silently reopening the order.

Handling the awkward cases

  • Underpaid — tell the customer the exact missing amount and that they can send it to the same payment page; partial payments add up.
  • Overpaid — deliver, then decide on store credit or a refund transfer according to your policy.
  • Expired — release stock and offer a fresh invoice if the customer still wants to buy.
  • Wrong network — this is handled in support, not code; show the network clearly at checkout to prevent it.
Security reminder: never put API or webhook secrets in client-side code, mobile apps or chat bots' message logic. Keep them on the server, restrict API access with an IP allow-list and use two-factor login for the merchant panel.

Testing and monitoring

  1. Expose a test endpoint — over HTTPS, with logging of headers and raw bodies.
  2. Trigger every event — paid, confirmed, underpaid, overpaid and expired, using small real payments.
  3. Replay a delivery — confirm your handler ignores the duplicate.
  4. Break the signature — change one byte and check the request is rejected.
  5. Watch delivery logs — the merchant dashboard shows each attempt, response code and transaction hash.
  6. Alert on failures — a spike in non-2xx responses means orders are not being fulfilled.

Bots and paid communities follow the same pattern; see how it fits a chat flow in accepting crypto payments in a Telegram bot.

Webhooks with mistKET

mistKET sends HMAC-signed webhooks for every invoice status, retries failed deliveries with back-off and shows delivery logs, expected and received amounts and transaction hashes in the merchant panel. Requests to the API are signed the same way, so one helper function covers both directions.

5invoice events
HMACSHA-256 signatures
Hoursof automatic retries

Review request and webhook samples in the developer section, check pricing and manage keys from the merchant login. To get access, apply on Telegram at @mistnetwork.

Frequently asked questions

What is a crypto payment webhook?
An HTTP request the gateway sends to your server whenever an invoice status changes, such as payment detected, confirmed, underpaid or expired.
Which event should trigger order fulfilment?
The confirmed event, after the required on-chain confirmations. The paid event only means the transfer was detected.
How do I verify a mistKET webhook?
Recompute HMAC-SHA256 over the timestamp, a dot and the raw body with your webhook secret, compare it in constant time with X-Mistket-Signature and reject stale timestamps.
Why did I receive the same webhook twice?
Deliveries are retried when your endpoint times out or errors. Make your handler idempotent by checking the order state before fulfilling.
What happens if my server is down?
Webhooks are retried with back-off for hours, and you can always check an invoice's status through the API or the merchant panel.

Related articles