Developer Guide
Webhooks
36min
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 voltage webhooks support the following event categories and their associated statuses send events events related to outgoing payments succeeded payment successfully sent failed payment failed to send receive events events related to incoming payments generated new payment request generated refreshed payment request refreshed expired payment request expired succeeded partial payment received (on chain only not applicable to bolt11 invoices) completed full payment received (entire requested amount paid) failed payment failed test events created test webhook created setting up webhooks 1\ create a webhook to create a webhook, make a post request to the webhooks endpoint 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", "url" "https //your domain com/webhook", "name" "production payment webhook", "events" \[ {"send" "succeeded"}, {"send" "failed"}, {"receive" "generated"}, {"receive" "completed"}, {"receive" "failed"} ] }' note in the example above, we subscribe to receive completed rather than receive succeeded because completed fires when the full payment amount is received (common for lightning/bolt11) succeeded only fires for partial payments (on chain only) most lightning payment integrations should listen for the completed event response { "id" "123e4567 e89b 12d3 a456 426614174000", "shared secret" "vltg gdtrrrjfj6afrraymw3t9rpxgcdct8zp" } important save the shared secret securely it's only shown once and is required for signature verification 2\ webhook configuration when creating a webhook, you can specify url the https endpoint that will receive webhook notifications name a descriptive name for your webhook events array of event types to subscribe to webhook payload structure headers each webhook request includes the following headers header description x voltage signature base64 encoded hmac sha256 signature for payload verification x voltage timestamp unix timestamp when the webhook was sent x voltage event the event type (e g , "send succeeded", "receive completed") payload format the webhook payload follows this structure { "type" "send", "detail" { "event" "succeeded", "data" { "id" "123e4567 e89b 12d3 a456 426614174000", "organization id" "123e4567 e89b 12d3 a456 426614174000", "environment id" "123e4567 e89b 12d3 a456 426614174000", "wallet id" "123e4567 e89b 12d3 a456 426614174000", "direction" "send", "currency" "btc", "type" "bolt11", "status" "succeeded", "data" { "amount msats" 100000, "max fee msats" 1000, "memo" "payment for invoice #1234", "payment request" "lnbc100n1p3slw4kpp5z4ybq0q0j6wkrkk99s9xg8p7rugry2nl0y4h8srq0l80r8gf8jhsdqqcqzpg " }, "error" null, "created at" "2024 11 21t18 47 04 008z", "updated at" "2024 11 21t18 47 04 008z" } } } example payloads successful send payment { "type" "send", "detail" { "event" "succeeded", "data" { "id" "payment 123", "direction" "send", "currency" "btc", "type" "bolt11", "status" "succeeded", "data" { "amount msats" 100000, "memo" "coffee payment", "payment request" "lnbc " } } } } partial payment received (on chain only) { "type" "receive", "detail" { "event" "succeeded", "data" { "id" "payment 456", "direction" "receive", "currency" "btc", "type" "onchain", "status" "succeeded", "data" { "amount msats" 150000, "requested amount msats" 250000, "description" "partial payment for invoice" } } } } full payment completed { "type" "receive", "detail" { "event" "completed", "data" { "id" "payment 789", "direction" "receive", "currency" "btc", "type" "bolt11", "status" "completed", "data" { "amount msats" 250000, "description" "invoice for services", "payment hash" "a1b2c3d4 " } } } } security & signature verification signature verification voltage signs all webhook payloads using hmac sha256 you should verify the signature to ensure the webhook is authentic and hasn't been tampered with verification steps extract the signature and timestamp from headers construct the message payload string + " " + timestamp calculate hmac sha256 using your shared secret base64 encode the result compare with the provided signature const crypto = require('crypto'); function verifywebhooksignature(payload, signature, timestamp, sharedsecret) { // create message from payload and timestamp const message = `${payload} ${timestamp}`; // create hmac const hmac = crypto createhmac('sha256', sharedsecret); hmac update(message); // get digest and encode const expectedsignature = hmac digest('base64'); // compare signatures return crypto timingsafeequal( buffer from(expectedsignature), buffer from(signature) ); }import hmac import hashlib import base64 def verify webhook signature(payload str, signature str, timestamp str, shared secret str) > bool """ verify the webhook signature using hmac sha256 args payload the raw webhook payload as a string signature the x voltage signature header value timestamp the x voltage timestamp header value shared secret your webhook's shared secret returns bool true if signature is valid """ \# create message from payload and timestamp message = f"{payload} {timestamp}" \# create hmac hmac obj = hmac new( shared secret encode('utf 8'), message encode('utf 8'), hashlib sha256 ) \# get digest and encode expected signature = base64 b64encode(hmac obj digest()) decode('utf 8') \# compare signatures (use constant time comparison) return hmac compare digest(expected signature, signature) testing webhooks using the test endpoint you can test your webhook implementation using the test endpoint 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", "currency" "btc", "direction" "receive", "status" "completed", "data" { "amount msats" 250000, "description" "test webhook" } } } } }' local testing with webhook tester for local development, you can use the provided webhook tester script set up the webhook tester python webhook tester py configure with your shared secret create a webhook config json webhook config json { "host" "localhost", "port" 7999, "secret" "vltg gdtrrrjfj6afrraymw3t9rpxgcdct8zp", "output file" "webhooks json", "log level" "debug" } expose locally using a tunnel service \# using smee io npm install g smee client smee url https //smee io/your channel target http //localhost 7999 \# or using ngrok ngrok http 7999 register the tunnel url as your webhook endpoint managing webhook deliveries delivery status webhook deliveries can have the following statuses attempting currently being delivered succeeded successfully delivered (2xx response) failed failed after all retry attempts abandoned manually abandoned viewing deliveries list all deliveries for a webhook curl 'https //voltageapi com/v1/organizations/{org id}/environments/{env id}/webhooks/{webhook id}/deliveries' \\ \ header 'x api key your secret token' retry failed deliveries retry a specific failed delivery curl 'https //voltageapi com/v1/organizations/{org id}/environments/{env id}/webhooks/{webhook id}/deliveries/{delivery id}/retry' \\ \ request post \\ \ header 'x api key your secret token' abandon deliveries stop retrying a delivery curl 'https //voltageapi com/v1/organizations/{org id}/environments/{env id}/webhooks/{webhook id}/deliveries/{delivery id}/abandon' \\ \ request post \\ \ header 'x api key your secret token' best practices understanding receive event types when handling receive events, it's important to understand the distinction between succeeded and completed succeeded fired when a partial payment is received this is only applicable to on chain transactions where partial payments are possible bolt11 (lightning) invoices cannot be partially paid completed fired when the full requested amount has been received this is the event you'll typically want to listen for to confirm that a payment has been fully satisfied for most use cases with lightning payments (bolt11), you'll only receive `completed` events when payments are successful 1\ always verify signatures never process a webhook without verifying its signature this ensures the webhook came from voltage and hasn't been tampered with 2\ respond quickly your endpoint should return a 2xx status code within 10 seconds for long running processes, acknowledge the webhook immediately and process asynchronously 3\ handle duplicates webhooks may be delivered more than once use the event id to ensure idempotent processing 4\ implement proper error handling const express = require('express'); const crypto = require('crypto'); // middleware to capture raw body for signature verification app use('/webhook', express raw({ type 'application/json' })); app post('/webhook', async (req, res) => { try { // get headers and raw body const signature = req headers\['x voltage signature']; const timestamp = req headers\['x voltage timestamp']; const rawbody = req body tostring('utf 8'); // verify signature if (!verifywebhooksignature(rawbody, signature, timestamp, process env webhook secret)) { return res status(401) json({ error 'invalid signature' }); } // parse payload const payload = json parse(rawbody); // queue for async processing (using your preferred queue system) await processwebhookasync(payload); // e g , bull, rabbitmq, etc return res status(200) json({ status 'accepted' }); } catch (error) { console error('webhook processing error ', error); return res status(500) json({ error 'internal error' }); } }); function verifywebhooksignature(payload, signature, timestamp, sharedsecret) { if (!signature || !timestamp || !sharedsecret) { return false; } 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) ); }@app route('/webhook', methods=\['post']) def handle webhook() try \# verify signature if not verify signature(request data, request headers) return jsonify({'error' 'invalid signature'}), 401 \# process webhook payload = request get json() process webhook async delay(payload) # queue for async processing return jsonify({'status' 'accepted'}), 200 except exception as e logger error(f"webhook processing error {e}") return jsonify({'error' 'internal error'}), 500 5\ monitor webhook health regularly check delivery statistics and failed deliveries to ensure your integration is working properly 6\ use https always use https endpoints for production webhooks to ensure data security 7\ rotate secrets periodically generate new webhook keys periodically for enhanced security curl 'https //voltageapi com/v1/organizations/{org id}/environments/{env id}/webhooks/{webhook id}/keys' \\ \ request post \\ \ header 'x api key your secret token' api reference webhook management operation endpoint description list webhooks get /organizations/{org id}/webhooks get all webhooks with filters create webhook post /organizations/{org id}/environments/{env id}/webhooks create new webhook get webhook get /organizations/{org id}/environments/{env id}/webhooks/{webhook id} get specific webhook update webhook patch /organizations/{org id}/environments/{env id}/webhooks/{webhook id} update webhook config delete webhook delete /organizations/{org id}/environments/{env id}/webhooks/{webhook id} delete webhook generate new key post /organizations/{org id}/environments/{env id}/webhooks/{webhook id}/keys rotate shared secret start webhook post /organizations/{org id}/environments/{env id}/webhooks/{webhook id}/start activate webhook stop webhook post /organizations/{org id}/environments/{env id}/webhooks/{webhook id}/stop deactivate webhook test webhook post /organizations/{org id}/environments/{env id}/webhooks/{webhook id}/test send test payload delivery management operation endpoint description list deliveries get / /webhooks/{webhook id}/deliveries get delivery history get delivery get / /webhooks/{webhook id}/deliveries/{delivery id} get specific delivery retry delivery post / /webhooks/{webhook id}/deliveries/{delivery id}/retry retry failed delivery abandon delivery post / /webhooks/{webhook id}/deliveries/{delivery id}/abandon stop retrying troubleshooting common issues signature verification failures ensure you're using the raw request body, not parsed json verify the shared secret matches exactly check timestamp is included in signature calculation webhooks not being delivered verify webhook status is "active" check your endpoint is publicly accessible ensure you're subscribed to the correct events duplicate webhooks implement idempotency using event ids track processed events in your database timeout errors return 200 immediately and process asynchronously optimize endpoint response time for additional support, contact the voltage support team