Using Webhooks with NakaPay
Webhooks allow your application to receive real-time notifications about payment events, such as when a payment is completed, fails, or expires. This is generally more reliable and efficient than polling the API for status updates.
Overview
When a significant event occurs related to one of your payments, NakaPay can send an HTTP POST request to a URL you specify. This request, known as a webhook, will contain a JSON payload with details about the event.
By using webhooks, you can automate actions in your backend system, such as:
- Updating an order status in your database.
- Sending a confirmation email to your customer.
- Logging transaction details for accounting.
- Triggering fulfillment processes.
Setting Up Your Webhook URL
You can configure your webhook endpoint URL in your NakaPay dashboard:
- Log in to your NakaPay Dashboard.
- Navigate to the "Profile & Webhook Settings" section.
- Enter your publicly accessible webhook URL in the "Webhook URL" field.
- Save your changes.
Alternatively, you can programmatically register and manage your webhook endpoint using the NakaPay Node.js SDK or by directly calling the API. This method offers more control, such as specifying which events trigger the webhook and managing your webhook secret.
NakaPay will then send POST requests to this URL for the subscribed events. Ensure your endpoint is prepared to receive these requests.
Webhook Events
NakaPay will send webhooks for the following payment-related events:
payment.completed
: A payment has been successfully completed.payment.failed
: A payment attempt has failed.payment.expired
: A payment request has expired without a successful payment.
Webhook Payload
All webhook notifications are sent as an HTTP POST request with a JSON body. Here's an example payload for a payment.completed
event:
1{
2 "event": "payment.completed",
3 "payment_id": "pay_1a2b3c4d5e6f",
4 "amount": 50000,
5 "description": "Premium Widget",
6 "status": "completed",
7 "timestamp": "2025-05-06T11:30:00.000Z",
8 "isNwc": true,
9 "nwcRelay": "wss://relay.getalby.com/v1",
10 "preimage": "abc123def456...",
11 "feeAmount": 250,
12 "feePercentage": 0.5,
13 "metadata": {
14 "orderId": "123",
15 "productId": "456"
16 }
17}
The payload structure is similar for other events, with the event
and status
fields reflecting the specific event type. Additional fields provide information about the payment processing mode:
isNwc
- Boolean indicating if the payment was processed non-custodially (true
) or custodially (false
)nwcRelay
- The Nostr relay used for non-custodial payments (only present whenisNwc
istrue
)preimage
- Payment preimage for completed payments (useful for proof of payment)feeAmount
- Fee amount in satoshisfeePercentage
- Fee percentage applied
Verifying Webhook Signatures
To ensure that webhook requests genuinely originate from NakaPay and have not been tampered with, we sign each webhook payload. The signature is included in an HTTP header, by defaultX-NakaPay-Signature
(this can be customized if you register webhooks via API).
The signature is generated using HMAC with SHA-256, using your unique webhook secret as the key. You obtain your webhook secret when you register or update a webhook endpoint via the NakaPay API. It is crucial to store this secret securely on your server.
Here's how to verify the signature in a Node.js environment:
1const crypto = require('crypto');
2
3function verifyWebhookSignature(rawBody, signature, secret) {
4 // rawBody should be the raw, unparsed JSON string from the request body.
5 // signature is the value from the X-NakaPay-Signature header.
6 // secret is your webhook secret.
7
8 try {
9 const expectedSignature = crypto
10 .createHmac('sha256', secret)
11 .update(rawBody) // Use the raw string body
12 .digest('hex');
13
14 // Use a constant-time comparison to prevent timing attacks
15 return crypto.timingSafeEqual(
16 Buffer.from(signature),
17 Buffer.from(expectedSignature)
18 );
19 } catch (error) {
20 console.error('Error verifying signature:', error);
21 return false;
22 }
23}
24
25// Example usage in an Express.js route:
26// Ensure you have a body parser that makes the raw body available, e.g.,
27// app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString(); } }));
28
29// app.post('/your-webhook-endpoint', (req, res) => {
30// const signature = req.headers['x-nakapay-signature']; // or your custom header
31// const webhookSecret = process.env.NAKAPAY_WEBHOOK_SECRET; // Load your secret securely
32//
33// if (verifyWebhookSignature(req.rawBody, signature, webhookSecret)) {
34// // Signature is valid, process req.body
35// console.log('Webhook received and verified:', req.body);
36// res.status(200).json({ received: true });
37// } else {
38// // Signature is invalid
39// console.warn('Invalid webhook signature received.');
40// res.status(400).json({ error: 'Invalid signature' });
41// }
42// });
Important: Always use the raw request body for generating the expected signature, as even minor changes in formatting (like whitespace) of a parsed-and-re-stringified JSON can lead to signature mismatches. Most web frameworks provide a way to access the raw body.
Responding to Webhooks
Your webhook endpoint should respond to NakaPay with an HTTP 2xx
status code (e.g., 200 OK
) as quickly as possible to acknowledge receipt of the event. If NakaPay does not receive a 2xx
response (or if the request times out), it will consider the delivery as failed and may attempt to retry.
It's a good practice to perform any time-consuming processing (like database updates or external API calls) asynchronously after sending the 200 OK
response. This prevents timeouts and ensures timely acknowledgment.
Webhook Retries
If NakaPay fails to deliver a webhook (e.g., your endpoint returns a non-2xx
status or times out), we will attempt to retry the delivery. Retries are typically performed with an exponential backoff strategy. The exact retry schedule and number of attempts can be configured if you register your webhook via the API.
Ensure your endpoint is idempotent, meaning it can safely handle receiving the same event multiple times without causing unintended side effects (e.g., by checking the payment_id
or a unique event ID if provided to see if it has already been processed).
Testing Webhooks Locally
During development, your local server is usually not accessible from the public internet. To test webhooks, you can use services that create a secure tunnel to your local machine, such as:
These tools provide a temporary public URL that you can set as your webhook URL in the NakaPay dashboard or API settings for testing purposes.
1# Example using ngrok (assuming your local server runs on port 3000)
2ngrok http 3000
3
4# ngrok will provide a public URL like https://xxxx-xx-xxx-xxx-xx.ngrok.io
5# Use this URL + your endpoint path (e.g., https://xxxx...ngrok.io/your-webhook-endpoint)
6# as your webhook URL in NakaPay settings.