Docs / Integration

Webhooks

Coinetic notifies your server of invoice changes with a POST request. A webhook is a signal to “go check the invoice,” not proof of payment.

Setup

  • Dashboard → “Keys & webhooks” → “Add.” The URL must be https:// on a public address and port 443 (the sandbox also allows http://localhost).
  • The endpoint secret is shown only once. You can have up to 5 active endpoints, and every event is sent to all of them.

Request

HTTP
POST /webhooks/coinetic HTTP/1.1
Content-Type: application/json
User-Agent: cryptopay-webhooks/1
X-Webhook-Id: evt_3f9c…
X-Webhook-Timestamp: 1760000000
X-Webhook-Signature: v1=5d1e…

{"id":"evt_3f9c…","type":"invoice.paid","created_at":"2026-10-12T15:04:05Z","livemode":true,"data":{"invoice_id":"inv_x7k2…","order_id":"ORDER-1001","status":"paid","paid_amount":"49990000"}}

X-Webhook-Id stays the same across retries. During secret rotation, X-Webhook-Signature contains two comma-separated signatures. The technical names (cryptopay-webhooks/1) are legacy; you don't need to change anything on your side.

Verifying the signature

text
expected = "v1=" + hex(HMAC-SHA256(webhook_secret, X-Webhook-Timestamp + "." + raw_body))
  1. Take the raw body: the bytes before any JSON parsing.
  2. Check that X-Webhook-Timestamp is within 5 minutes of the current time.
  3. Split X-Webhook-Signature on commas. The signature is valid if any part matches the expected value (use a constant-time comparison).
  4. No match? Respond with 401 and do nothing.

Test vector: secret whsec-0123456789abcdef0123456789abcdef, timestamp 1760000000, body {"id":"evt_1","type":"invoice.paid"} → v1=aceeb56215ba09e7a2923d48f89afb8331f422f2d0a8cf60f76fef69fe06a6a4.

Handling

text
verify signature → deduplicate by X-Webhook-Id → GET /v1/invoices/{invoice_id}
→ update the order from the API status (fulfill once) → respond 2xx
  • Deduplication. The same event can arrive more than once. Store the X-Webhook-Id values you've processed, and set the “fulfilled” flag in the same transaction as the fulfillment itself.
  • Respond quickly, within 10 seconds. Push long-running work to a queue.
  • Response codes: 2xx means accepted; 410 Gone means don't retry this delivery; any other code (including 3xx, since redirects aren't followed) or a timeout triggers a retry.
  • Ignore unknown fields and event types: the list will grow.

Ready-made handlers in four languages are in the quickstart.

Retries

10 s → 30 s → 2 min → 10 min → 30 min → 1 h → 3 h ×7 → 1 h, with slight jitter. The final attempt comes roughly 24 hours later. You can resend a failed delivery from the dashboard: “Webhooks” → “Resend.”

Ordering

Events for the same invoice are delivered to an endpoint in order: the next one isn't sent while the previous delivery is still awaiting a retry. Once that delivery finishes, successfully or not, the following events go out. So rely on the status from GET /v1/invoices/{id}, not on the sequence of events. Events for different invoices are independent.

Event types

EventWhen
invoice.detectedPayment seen on the network
invoice.confirmingPayment is in a block, confirmations in progress
invoice.paidPaid and confirmed; safe to fulfill (after re-checking). data.late: true means a stablecoin arrived after expiration and was credited automatically
invoice.overpaidPaid with an excess
invoice.underpaidConfirmed amount is short
invoice.awaiting_paymentThe invoice is awaiting payment again (for example, after a reorg of its only payment)
invoice.expiredExpired with no payment
invoice.late_paymentPayment in a volatile asset after expiration
invoice.manual_reviewPayment under AML review
invoice.payment_heldPayment held by AML (quarantine)
invoice.reorgedThe block containing the payment was rolled back and the status recalculated (for example, paid → confirming)
invoice.payment_after_closeA payment arrived on an already closed invoice: it doesn't pay for the order, but it's available for refund
invoice.cancelledThe invoice was closed unpaid by an executed refund
refund.sentRefund executed; data.refund contains the amount, address and tx_hash

Billing events (billing.*) cover platform fee invoices: issued, paid, overdue, suspended and reinstated.

Digital goods and invoice.reorgedA reorg after paid is rare, but it happens. If you deliver goods instantly, subscribe to this event and route these cases to support.

Rotating the secret

Dashboard → “Rotate secret”: Coinetic starts signing with both secrets.

  1. Add the new secret to your server (accept both).
  2. Click “Complete rotation” to delete the old one.
  3. Remove the old secret from your server.