Skip to main content
Webhooks notify your application when a request finishes. Use them to wake a backend or update a payment record without keeping a wallet event listener running. A webhook does not claim incoming funds or grant access to private wallet history.

Register a webhook

For Lightning receives, register on the wallet that creates the invoice. That wallet receives notifications even when the invoice names another receiver. Receiver-side delivery is not a substitute for registering on the requesting wallet.
The SDK’s webhook methods use event_types and webhook_id, even though most wallet methods use camelCase. Use a random ASCII secret of at least 16 characters. The example generates 32 random bytes and registers them as a 64-character hex string. The endpoint must use HTTPS, and its hostname must resolve when registering. A newly created tunnel can fail with Failed to resolve hostname; wait for DNS, then retry registration with bounded backoff. Do not mark notifications as enabled until registration succeeds.
Up to five webhook URLs can be registered per wallet. Registering the same URL updates its secret and event list; it does not create another registration or append events. Adding a sixth URL evicts the oldest registration, so list existing registrations before adding another.

Verify the raw request body

Each webhook includes X-Spark-Signature: a hex-encoded HMAC-SHA256 of the raw HTTP body, keyed with the registered secret. Use SHA-256; implementations for other products with a similarly named header are not interchangeable.
Capture the bytes before JSON parsing and pass the header value to this function. Do not verify JSON.stringify(parsedBody): whitespace and serialization changes alter the signature. The generated secret is used as the literal string provided when registering the webhook, not decoded from hex into bytes. Reject missing or invalid signatures. After verification, validate the payload and durably record or queue the event before acknowledging it. Signature verification does not prevent replay; processing must also be idempotent.

Match a Lightning receive

Persist the full receive-request ID and invoice.paymentHash when creating an invoice. The webhook’s receive-request id is a bare UUID, while the SDK returns SparkLightningReceiveRequest:<UUID>. A direct string comparison misses the match. Normalize the request ID for the database lookup while retaining the original SDK ID for API calls:
This normalization is specific to Lightning receive requests. Do not strip prefixes from every request identifier, and do not mistake a delivery ID or webhook registration ID for a receive-request ID. The receive notification includes request details such as id, status, request_status, payment_preimage, receiver_identity_public_key, and amounts. The receive payload does not include the Spark transfer ID. Look up the stored SDK request ID as the requester or receiver and read request.transfer?.sparkId. If the transfer is not present yet, retry the lookup later. See invoice-to-transfer correlation. A SPARK_LIGHTNING_RECEIVE_FINISHED notification reports a terminal outcome; the name alone is not proof of success. Check request_status and the receive-flow status before crediting a payment. Reconcile pending requests through lookups as well: do not assume every failure or cancellation will produce a notification. Request completion is separate from the receiver claiming the funds and making them spendable. Amounts are objects, not bare numbers. For example, { "value": 1000000, "unit": "MILLISATOSHI" } represents 1,000 sats. Do not assume htlc_amount always uses the same unit or is always present. SDK amount objects use originalValue and originalUnit instead.

Delivery and retries

Webhook delivery is retried for HTTP 408, 429, and 5xx responses, as well as unexpected transport failures. Other client errors, including 401, are not retried. A registration-time DNS error means no webhook was registered; it is separate from a delivery failure after registration. Use a retryable response when you cannot durably accept an event. A short observation window without another delivery is not evidence that retries are disabled. Do not depend on a fixed retry interval, count, or ordering between webhooks, operator events, and payment-request lookups. Handle repeated notifications without applying the same payment twice. Keep pending requests in your database and reconcile them through payment-request lookups after downtime; a missing webhook is not proof that a payment failed.

Event types