Developer Guide
Webhooks
26 min
voltage payments webhooks documentation overview webhooks allow your application to receive real time notifications about events happening in your voltage payment system instead of polling for updates, voltage will send http post requests to your specified endpoint whenever relevant events occur event types each webhook payload has { "type" "send" | "receive" | "test", "detail" { "event" " ", "data" { } } } where type is the category send , receive , or test detail event is a more specific event within that category (enums below) send events enum sendeventtypes succeeded – payment fully sent and recorded failed – payment failed (exhausted routes, insufficient funds, or similar) receive events enum receiveeventtypes generated – a receive payment was created (invoice/address/bip21 generated) refreshed – receive request was refreshed (e g address rotation) expired – invoice/address expired before full payment succeeded – partial payment received (on‑chain only; not applicable to bolt11) completed – full requested amount received failed – receive flow failed (error generating, compliance issue, etc ) for most lightning (bolt11) integrations, you’ll primarily listen for receive completed test events enum testeventtypes created – test webhook event used by the /test endpoint and internal tooling webhook objects webhook status webhookstatus enum active – delivering events stopped – temporarily disabled deleted – removed (retained only for history) webhook json shape webhookread looks like { "id" "b0fc9829 f139 4035 bb14 4a4b6cd58f0e", "organization id" "b0684ab8 1130 46af 8f70 71519442f108", "environment id" "123e4567 e89b 12d3 a456 426614174000", "url" "https //your domain com/webhook", "name" "production payment webhook", "events" \[ { "send" "succeeded" }, { "send" "failed" }, { "receive" "completed" }, { "receive" "failed" } ], "status" "active", "created at" "2025 04 29t17 09 11 299z", "updated at" "2025 04 29t17 09 11 299z", "stopped at" null, "deleted at" null } the events array is an array of eventtypes objects each object has exactly one of send , receive , or test whose value is the corresponding enum ( sendeventtypes , receiveeventtypes , testeventtypes ) to subscribe to multiple events you include multiple entries "events" \[ { "send" "processing" }, { "send" "succeeded" }, { "send" "failed" }, { "receive" "generated" }, { "receive" "completed" } ] setting up webhooks 1\ create a webhook endpoint post /v1/organizations/{organization id}/environments/{environment id}/webhooks body (newwebhookrequest) curl "https //voltageapi com/v1/organizations/{organization id}/environments/{environment id}/webhooks" \\ \ request post \\ \ header "content type application/json" \\ \ header "x api key your secret token" \\ \ data '{ "id" "b0fc9829 f139 4035 bb14 4a4b6cd58f0e", "organization id" "{organization id}", "environment id" "{environment id}", "url" "https //your domain com/webhook", "name" "production payment webhook", "events" \[ { "send" "succeeded" }, { "send" "failed" }, { "receive" "generated" }, { "receive" "completed" }, { "receive" "failed" } ] }' note organization id and environment id in the body must match the path parameters response (202 – webhookplainsecret) { "id" "b0fc9829 f139 4035 bb14 4a4b6cd58f0e", "shared secret" "vltg gdtrrrjfj6afrraymw3t9rpxgcdct8zp" } save shared secret securely – it’s only returned once and is required for signature verification 2\ list webhooks endpoint get /v1/organizations/{organization id}/webhooks?environment ids={env id 1},{env id 2} returns an array of webhookread objects curl "https //voltageapi com/v1/organizations/{organization id}/webhooks?environment ids={environment id}" \\ \ header "x api key your secret token" 3\ get / update / delete a webhook endpoints get /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id} patch /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id} delete /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id} update body ( updatewebhookrequest ) – currently you can update url , name , and events { "url" "https //your domain com/new webhook", "events" \[ { "receive" "completed" }, { "receive" "failed" } ] } 4\ start / stop / rotate keys / test endpoints post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/start post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/stop post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/keys post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/test rotate keys returns a new webhookplainsecret (same shape as create) test webhook expects testwebhookrequest curl "https //voltageapi com/v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/test" \\ \ request post \\ \ header "content type application/json" \\ \ header "x api key your secret token" \\ \ data '{ "delivery id" "123e4567 e89b 12d3 a456 426614174002", "payload" { "type" "receive", "detail" { "event" "completed", "data" { "id" "test payment 123", "direction" "receive", "currency" "btc", "type" "bolt11", "status" "completed", "wallet id" "{wallet id}", "organization id" "{organization id}", "environment id" "{environment id}", "requested amount" { "amount" 250000, "currency" "btc", "unit" "msats" }, "data" { "payment request" "lntbs1500n1pn5w25y ", "amount" { "amount" 250000, "currency" "btc", "unit" "msats" }, "memo" "test webhook" } } } } }' this shape matches testwebhookrequest ( delivery id + generic payload ) webhook payload structure headers each webhook request includes x voltage signature – base64‑encoded hmac‑sha256 of the payload and timestamp x voltage timestamp – unix timestamp when the webhook was generated x voltage event – flattened event string like send succeeded , receive completed , test created generic payload the payload schema is a tagged union { "type" "send", "detail" { "event" "succeeded", "data" { / payment / } } } where type = " send " | " receive " | " test " detail event = sendeventtypes / receiveeventtypes / testeventtypes detail data for send / receive a full payment object for test arbitrary test payload (commonly a string) payment shape (inside detail data) webhook send / receive payloads embed the same payment object you get from the payments api example (lightning receive) { "id" "11ca843c bdaa 44b6 965a 39ac550fcef7", "direction" "receive", "wallet id" "{wallet id}", "organization id" "{organization id}", "environment id" "{environment id}", "created at" "2024 11 21t18 47 04 008z", "updated at" "2024 11 21t18 47 04 008z", "currency" "btc", "type" "bolt11", "status" "receiving", "requested amount" { "amount" 150000, "currency" "btc", "unit" "msats" }, "data" { "payment request" "lntbs1500n1pn5w25y ", "amount msats" 150000, "memo" "testing" }, "error" null } amount msats and/or amount sats may be present for backwards compatibility, but the recommended shape is the amount object ( amount + currency + unit ) wherever available example webhook payloads successful send (lightning) { "type" "send", "detail" { "event" "succeeded", "data" { "id" "payment 123", "direction" "send", "currency" "btc", "type" "bolt11", "status" "completed", "wallet id" "{wallet id}", "data" { "payment request" "lntbs1500n1pn5w25y ", "amount" { "amount" 100000, "currency" "btc", "unit" "msats" }, "amount msats" 100000, "max fee" { "amount" 1000, "currency" "btc", "unit" "msats" }, "max fee msats" 1000, "memo" "coffee payment" } } } } partial on‑chain receive { "type" "receive", "detail" { "event" "succeeded", "data" { "id" "payment 456", "direction" "receive", "currency" "btc", "type" "onchain", "status" "succeeded", "requested amount" { "amount" 250000, "currency" "btc", "unit" "msats" }, "data" { "address" "tb1pzkhtj4ld8 ", "amount" { "amount" 150000, "currency" "btc", "unit" "msats" }, "amount msats" 150000, "description" "partial payment for invoice", "receipts" \[ { "amount sats" 1500, "height mined at" 1888021, "tx id" "a22ec88f7a84a705 " } ] } } } } full payment completed (lightning) { "type" "receive", "detail" { "event" "completed", "data" { "id" "payment 789", "direction" "receive", "currency" "btc", "type" "bolt11", "status" "completed", "requested amount" { "amount" 250000, "currency" "btc", "unit" "msats" }, "data" { "payment request" "lntbs1500n1pn5w25y ", "amount" { "amount" 250000, "currency" "btc", "unit" "msats" }, "amount msats" 250000, "description" "invoice for services", "payment hash" "a1b2c3d4 " } } } } security & signature verification voltage signs all webhook payloads using hmac‑sha256 with your shared secret you should always verify the signature before trusting the payload verification steps read headers x voltage signature x voltage timestamp read the raw request body (as a string) build the message payload + " " + timestamp compute hmac sha256(shared secret, message) and base64‑encode it compare to x voltage signature using a timing‑safe comparison example in node js (unchanged, just names aligned) const crypto = require("crypto"); function verifywebhooksignature(payload, signature, timestamp, sharedsecret) { const message = `${payload} ${timestamp}`; const hmac = crypto createhmac("sha256", sharedsecret); hmac update(message); const expectedsignature = hmac digest("base64"); return crypto timingsafeequal( buffer from(expectedsignature), buffer from(signature) ); } managing webhook deliveries delivery status deliverystatus enum attempting – currently being delivered / will be retried succeeded – delivered with a 2xx response failed – exhausted retries without success abandoned – manually abandoned (no further retries) list deliveries endpoint get /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/deliveries response is webhookdeliveries { "items" \[ { "id" "delivery 1", "webhook id" "b0fc9829 f139 4035 bb14 4a4b6cd58f0e", "url" "https //your domain com/webhook", "status" "succeeded", "status code" 200, "payload" { "type" "receive", "detail" { "event" "completed", "data" { " " "full payment object" } } }, "attempt count" 1, "error" null, "created at" "2025 04 29t17 09 11 299z", "updated at" "2025 04 29t17 09 11 400z" } ], "offset" 0, "limit" 50, "total" 1 } get / retry / abandon a delivery endpoints get /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/deliveries/{delivery id} post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/deliveries/{delivery id}/retry post /v1/organizations/{organization id}/environments/{environment id}/webhooks/{webhook id}/deliveries/{delivery id}/abandon use these to inspect, retry, or stop retrying individual deliveries best practices listen for receive completed for ln/bolt11 success; receive succeeded is only for partial on‑chain receives always verify signatures using your shared secret respond quickly (within 10 seconds); offload heavy work to background jobs treat webhook processing as idempotent – dedupe with delivery ids monitor delivery stats and retry/abandon failed deliveries as needed use https endpoints and rotate secrets via /keys periodically