Webhook notifications
The sidecar can send webhook notifications when payments are finalized (completed or failed). This is useful for tracking payment status without polling.
Set LEXE_WEBHOOK_URL in your environment or pass --webhook-url to the
sidecar binary:
# Via environment variable
$ export LEXE_WEBHOOK_URL="https://example.com/webhooks/lexe"
# Via CLI argument
$ lexe-sidecar --webhook-url https://example.com/webhooks/lexe
When a payment is finalized, the sidecar will POST a JSON payload to your webhook URL:
{
// Fields from the "Standard Webhooks" spec: https://www.standardwebhooks.com
"type": "payment.finalized",
"timestamp": "2025-04-17T21:54:17.989Z",
// Originating wallet, useful for multi-wallet setups
"user_pk": "63ad1661bfc23ad25f5bcc6f610f8fd70d7426de51be74766c24e47f4b4fcfca",
// Payment fields
"index": "0000001744926519917-ln_9be5e4e3a0356cc4a7a1dce5a4af39e2896b7eb7b007ec6ca8c2f8434f21a63a",
"rail": "invoice",
"kind": "invoice",
"direction": "inbound",
"hash": "9be5e4e3a0356cc4a7a1dce5a4af39e2896b7eb7b007ec6ca8c2f8434f21a63a",
"preimage": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
"offer_id": null,
"txid": null,
"amount": "1000",
"fees": "0",
"partner_pk": null,
"partner_prop_fee": null,
"partner_base_fee": null,
"status": "completed",
"status_msg": "completed",
"address": null,
"invoice": "lnbc10n1p5qz7z2dq...",
"tx": null,
"payer_name": null,
"message": null,
"personal_note": null,
"priority": null,
"expires_at": 1744930119917,
"finalized_at": 1744926857989,
"created_at": 1744926519917,
"updated_at": 1744926857989
}
The payment fields use the same response schema as GET /v2/node/payment,
flattened alongside a user_pk field that identifies the wallet (useful for
multi-wallet setups) and top-level type and timestamp fields from the
Standard Webhooks spec.
Your endpoint should return a 200 status code to indicate success.
NOTE: All webhook handling must be idempotent. In rare occasions, you might receive a payment notification more than once.
(Optional) Authenticating webhook events
Webhook authentication is needed if your webhook receiver is publicly
reachable; for example, LEXE_WEBHOOK_URL=https://myapp.com/api/webhooks.
If you run the webhook receiver on the same machine as the sidecar
(e.g. LEXE_WEBHOOK_URL=http://localhost:8000), you can skip this section.
Otherwise, anyone who can reach your webhook receiver could POST a fake webhook
and trick your receiver into thinking a payment was completed when it wasn't.
To prevent this, you should configure the sidecar with a secret shared between the sidecar and the webhook receiver, and verify the signature on every request.
Lexe's sidecar signs outbound webhooks using the
Standard Webhooks HMAC-SHA256 scheme.
The shared secret is a random 24–64 byte string, base64-encoded and
conventionally prefixed with whsec_.
To generate one, use any cryptographically secure RNG, e.g.:
# Using openssl
$ openssl rand -base64 24 | sed 's/^/whsec_/'
whsec_yb5FARH2Kl2CCKkVB9bHXPuLpEy4v9qR
# Using python3
$ python3 -c 'import secrets, base64; print("whsec_" + base64.b64encode(secrets.token_bytes(24)).decode())'
whsec_Ynf2Ro771KA7e/kDOxu+4iWy/7OJExE4
Then configure the sidecar with the secret via LEXE_WEBHOOK_SECRET or
--webhook-secret:
# Via environment variable
$ export LEXE_WEBHOOK_SECRET="whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oD"
# Via CLI argument
$ lexe-sidecar --webhook-secret whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oD
If a secret is configured, the sidecar adds three additional headers to every webhook request it sends:
webhook-id: An opaque identifier for this delivery, stable across retries. Safe to use as an idempotency key.webhook-timestamp: The unix timestamp (seconds) of the delivery attempt.webhook-signature: The signature for this delivery (see below).
Each signature is an HMAC-SHA256 over the raw HTTP body, event id, and delivery
timestamp, tagged with a v1 prefix, and signed using the shared secret.
Specifically, the webhook-signature header value is
"v1," + base64(HMAC-SHA256(<secret>, "<id>.<timestamp>.<body>")).
Full details are in the
Standard Webhooks spec.
To verify signatures, the easiest way is to use one of the official Standard Webhooks libraries — implementations exist for C#, Elixir, Go, Java, JavaScript, PHP, Python, Ruby, and Rust. Alternatively, verify the signatures yourself; the full spec is short and easy to follow.
Delivery guarantees and persistence
For each finalized payment, the sidecar attempts delivery up to three times
with exponential backoff. To ensure that delivery attempts are made for every
payment created by the sidecar, even across sidecar restarts, the sidecar
persists the set of watched payments to ~/.lexe/ (override with
LEXE_DATA_DIR or --data-dir).
NOTE: Any per-request client credentials submitted via the Authorization
header are also persisted alongside the tracking state so the sidecar can
resume polling them on restart.