Skip to content

Crypto payment webhooks: fulfill each order only once

Build a reliable crypto payment webhook receiver: verify signatures, persist events, deduplicate retries, and prevent duplicate order fulfillment.

WebhooksFulfill once

A valid payment notification should not deliver the same order twice. Crypto payment webhooks need two separate protections: verify who sent the event, then make its business effect safe to repeat. A signature answers the first question. Idempotency answers the second.

This guide is for developers connecting one-time digital purchases, licenses or account credits to Yolfi. It explains the native webhook format and a recommended application design, not a ready-made fulfillment service. For subscriptions, access also depends on the subscription lifecycle; do not reuse a one-time order rule for every renewal.

Why a genuine webhook can still cause a duplicate

A receiver can save an event successfully while its HTTP response is lost. The sender then retries. Your worker can also crash after granting access but before recording completion. Neither situation implies a second customer payment.

Yolfi's webhook event reference documents at-least-once delivery and independent retries for multiple endpoints. Design for the same event arriving again, possibly while the first delivery is still processing. Do not depend on a particular retry schedule or assume notifications always reach your application in creation order.

Keep three identities separate:

Identity What it identifies Recommended control
Signed event id One provider event, including repeated deliveries Unique event record within the provider, organization and environment scope
Invoice or payment reference One payment attempt Stored relationship to the intended commercial order
Business order reference What the customer should receive Unique fulfillment action for that order

Event deduplication alone is not enough: two successful attempts for one order can have different event IDs. Order-level protection prevents a second license or credit while the extra receipt is reviewed separately.

Verify the raw request before trusting its fields

Follow the current Yolfi signature instructions. Native requests use X-Yolfi-Signature, a Base64-encoded HMAC-SHA256 over the raw request body, with that endpoint's signing secret. The organization API key is not the webhook secret.

Capture the original body before JSON middleware changes it. Compute and compare the signature with a constant-time comparison function; reject missing or malformed signatures without executing business actions. Keep signing secrets in a secrets manager or protected environment configuration, never in source files or support logs.

Only after verification should you parse and validate the payload. Use the signed payload id for deduplication. X-Yolfi-Event-ID is a convenience copy, not a separately signed value; if used, require it to equal the payload ID. Bind the receiving endpoint to its expected organization and validate orgId where present. User-supplied metadata must not decide tenant authorization.

A valid signature does not make an old delivery new. Retaining processed event IDs is still necessary. Yolfi's documented signature is not a timestamp-based freshness check, so do not claim that HMAC verification by itself prevents replay.

Save first, acknowledge second, process asynchronously

Use a durable inbox: an application table that records an authenticated event and its processing state. Insert the event under a database uniqueness constraint, not a separate “does it exist?” check followed by an unprotected insert. Concurrent requests can both pass that check.

Return 200 OK promptly after durable acceptance, including when an authenticated duplicate is already stored. If persistence fails, do not acknowledge the event as accepted. A fast response followed by an in-memory task is not durable: a restart can lose the only copy your application kept.

A worker can then load the saved event, validate its order relationship and apply the business transition. Keep failed work visible with a retry count, next action and alert owner. Retain only necessary payload data in restricted storage; event bodies can contain customer information.

Gate fulfillment on confirmation and the correct order

The Yolfi webhook guide identifies payment.confirmed as the primary fulfillment trigger. invoice.created means an invoice was created, not that the customer paid. A browser redirect or screenshot is not a substitute for verified server-side confirmation.

Resolve the event to a server-stored order. In the native flow, standalone API Checkout events can include checkoutSessionId and merchantOrderId without paylinkId. Do not reject a valid standalone purchase merely because a Paylink ID is absent. Equally, a reusable Paylink ID alone does not uniquely identify a buyer's order.

Check the expected organization, environment, payment attempt and commercial amount context before release. The documented amount is the final merchant receivable in the payment token; it is not automatically your original invoice total in local currency. Do not compare those two numbers as if they had the same units. Keep the original server-approved order and its pricing context.

For dynamic purchases, use the API Checkout flow to retain the order-to-session relationship. For fixed products, payment links provide the reusable payment entry point; your application still owns the delivery decision.

Protect the side effect, not just the event table

For an order fulfilled entirely inside your database, record the event outcome and the order transition in one transaction. Enforce a unique business action, such as one entitlement grant per one-time order. A duplicate event should return the existing result, not grant another entitlement.

If fulfillment calls another service, use a durable outbox: save the intended action with the order transition, then let a worker deliver it. Give the downstream action a stable order-based idempotency key where the recipient supports one. The outbox alone does not prevent a remote duplicate if a worker crashes after the remote action succeeds.

When a destination has no idempotency support or result lookup, leave an uncertain outcome for reconciliation instead of blindly retrying an irreversible action. “Exactly once” is not something an HTTP webhook can promise across every database, queue and external service.

Keep stale events from moving a completed order backward. An invoice.overdue event concerns one attempt and must not erase a fulfilled order. For recurring access, the provider distinguishes invoice events from subscription.overdue and subscription.cancelled; implement the documented subscription policy separately.

Test the failure paths before accepting live orders

Use these acceptance cases for your receiver and worker:

  • Deliver the same signed event twice and concurrently: one stored event, one business effect.
  • Interrupt the worker before and after the order transition: recovery must not grant access twice.
  • Deliver two confirmed attempts for one order: fulfill once and flag the additional receipt.
  • Send an invalid signature, a mismatched organization and an unknown order: no fulfillment.
  • Deliver an older invoice event after confirmation: no rollback of completed fulfillment.
  • Send a test-mode event to the production receiver: no real license, shipment or account credit.

Yolfi's dashboard test events use livemode: false and an evt_test_ ID. They exercise delivery and payload handling but do not create real payment records. Separate test and live effects explicitly; a successful test notification is not proof of a settled purchase.

Monitor authenticated events awaiting processing, worker failures and confirmed payments without a fulfillment result. Connect these exceptions to the payment reconciliation checklist. If the second receipt is a genuine additional payment rather than a retry, follow the refund review procedure instead of deleting the evidence.

Frequently asked questions

Does signature verification prevent duplicate credits?

No. It verifies authenticity and integrity. A genuine event can arrive more than once, and a previously valid request can be replayed. Persist event IDs and enforce an order-level business constraint.

Should I return success before saving the event?

No. Acknowledge after durable acceptance, then perform slower work asynchronously. Otherwise a crash after the response can silently discard the event.

Is a transaction hash a sufficient deduplication key?

Not for webhook processing. Use the documented signed event ID for event deliveries and your own order key for fulfillment. Transfers and orders have different identities; one transaction can contain multiple transfers.

Can I use the same handler for adapter payloads?

Not without checking their contract. This article describes the native format with adapter NONE. Provider-compatible adapters can have a different payload shape; validate their documented fields before reusing native parsing and correlation rules.

Conclusion: confirm, persist and fulfill once

Reliable crypto payment automation separates authenticated delivery from the order's business effect. Verify the raw signature, persist the event, match the order and make fulfillment safe under retries. Start with Yolfi payment links for fixed products or the documented server-created session for dynamic orders, and keep the application responsible for granting each purchase only once.

Start accepting crypto payments today

Connect a wallet, create your first link and get paid in minutes.

Business owner working at a laptop
Payment received+120.00 USDT