Webhooks

Events and payload

The webhook events Solverr sends, their JSON payload and signed headers, how to set up your endpoint, and which events a shop must handle.

On this page

Solverr sends a signed POST request to your webhook URL whenever an invoice, refund, payout, subscription or support case changes. Use these events to mark orders as paid on your server without polling. Never fulfill orders from browser events.

Set up your endpoint

Each business has one webhook URL and one signing secret. Only the owner can change them.

  1. Open Dashboard → Developers and find the Webhook card.
  2. In Endpoint URL (https), enter your endpoint URL, for example https://shop.example/webhooks/solverr, and select Save.
  3. Store the signing secret, which starts with whsec_ and is shown once, on the server that receives events, for example as SOLVERR_WEBHOOK_SECRET.
  4. Select Send ping event to check that your endpoint receives and verifies events (see Test your webhook endpoint).

To turn webhooks off, clear the URL, select Turn webhooks off, then select Turn off to confirm. While no URL is saved, events are not queued and are never sent later.

Payload

Every event is a JSON object with the same five fields:

{
  "id": "evt_5tR8mW2xQpL7vN3kB9sZ",
  "type": "invoice.paid",
  "mode": "live",
  "created_at": "2026-11-05T12:03:41.000Z",
  "data": {
    "invoice": { "id": "inv_7h3K9dWq2LmN4pRt6vXy", "object": "invoice", "status": "paid", "order_id": "1042" },
    "payment": { "id": "quo_4kT9mW2xQpL7vN3rB8sZ", "object": "payment", "status": "success" }
  }
}

The example shortens invoice and payment. Real events carry the full invoice and payment objects.

FieldMeaning
idUnique event id starting with evt_, sent again unchanged on every retry
typeThe event type from the table below
modelive for every payment in your business
created_atWhen the event happened
dataThe objects the event is about

Request headers

Solverr signs events the Standard Webhooks way, so any Standard Webhooks library can verify them.

HeaderValue
webhook-idThe event id
webhook-timestampUnix time in seconds when this attempt was signed
webhook-signaturev1, and the base64 HMAC-SHA256 of id.timestamp.raw body; while a rotated secret is still valid, a second v1, signature follows after a space
Content-Typeapplication/json
User-AgentSolverr-Webhooks/1

Verify signatures shows how to check them.

Event types

TypeWhen it is sentdata
invoice.detectedThe customer's payment was seen on its chaininvoice, payment
invoice.paidYou received the coin you chose, including for a late payment, which has invoice.late set to trueinvoice, payment
invoice.underpaidLess than the needed amount arrived, and NEAR Intents refunds itinvoice, payment
invoice.refundedThe customer's payment was returned to their refund addressinvoice, payment
invoice.failedThe swap failed, and Solverr opens a support caseinvoice, payment
invoice.expiredThe invoice reached its lifetime while it was still waiting for a payment, or a detected payment ended unpaid after that time. Not sent for an invoice that ended underpaid, refunded or failed, which keeps that status, or for an invoice opened from a payment link where the customer never chose a coin, since that was only a page visitinvoice
invoice.extra_paymentA second payment for an already paid invoice also succeededinvoice, payment
refund.completedA refund you approved reached the customerrefund
refund.failedA refund you sent did not complete; failure_reason says why, and its amount can be refunded againrefund
refund.canceledA refund was canceled before it was sent; its amount can be refunded againrefund
payout.completedA payout you approved reached the recipientpayout
payout.failedA payout you sent did not complete; failure_reason says whypayout
payout.canceledA payout was canceled before it was sentpayout
subscription.invoice_createdA subscription created its next invoiceinvoice
support_case.createdA customer opened a support case from the help page. Sent only while the business has at most 30 support cases in the last 24 hours, counting the cases Solverr opens itself for failed swaps, which never send this event; later cases appear only on the Support pagesupport_case, invoice
test.pingYou sent a ping event from Dashboard → Developers or the APImessage

Which events to handle

For a shop, invoice.paid is the event that matters: mark the order paid when it arrives. These cases need extra care:

  • invoice.paid with late: true. The customer paid to an address that was already replaced or expired, but the money still reached you. Complete the order or refund it.
  • invoice.expired, then invoice.paid. A late payment can still arrive within 24 hours after expiry. Do not treat invoice.expired as final if you can still fulfill the order.
  • invoice.detected is not final. If that payment is returned or fails, the invoice goes back to where the customer's newest payment attempt stands. When that attempt is still waiting for a payment, no event is sent until the next change. When it already ended, or the invoice's lifetime has passed, you get that status's event: invoice.underpaid, invoice.refunded, invoice.failed or invoice.expired.
  • invoice.extra_payment. The customer paid twice. Return the second payment with Refund this extra payment on the invoice's page in Dashboard → Payments (see Refunds).
  • invoice.underpaid and invoice.refunded. No money reached you. After invoice.underpaid, NEAR Intents returns the payment to the customer, and invoice.refunded follows. The customer can then try again from the checkout page while at least 2 minutes of the invoice's lifetime remain.
  • No invoice.expired after underpaid, refunded or failed. An invoice that ended in one of these statuses keeps it after expires_at. Treat it as unpaid once expires_at has passed, unless a late invoice.paid arrives.

Good practice

  • The same event can arrive more than once. Store each id you handled and skip repeats.
  • Events can arrive out of order when a retry is involved. Trust the status in the event's invoice, not the order of arrival.
  • Answer with any 2xx status within 10 seconds, and do slow work after you answer.