Webhooks
Webhooks allow you to build or set up integrations that subscribe to certain events on Pynath Booking. When one of those events is triggered, we'll send a HTTP POST payload to the webhook's configured URL.
Setting up a Webhook
You can manage your Webhook endpoints from the Integrations & API > API & Webhooks tab in your Pynath Booking Dashboard.
- Click Add Endpoint.
- Enter the HTTPS URL where you want to receive events.
- Copy your unique Signing Secret. You will need this to verify requests.
Local Development
To test webhooks on your local machine before deploying your code, we strongly recommend using ngrok. Ngrok creates a secure, publicly accessible tunnel to your localhost environment.
1. Start your local server
Run your application on a local port, for example 3000.
2. Start ngrok
Expose your local port to the internet using the following terminal command:
$ ngrok http 30003. Configure your webhook
Copy the forwarding URL (e.g., https://1a2b-3c4d.ngrok.io) and paste it into your Pynath Booking dashboard as your new endpoint URL.
Verifying Signatures
Because your webhook URL is public, anyone could send a POST request to it. To verify that a request genuinely came from Pynath Booking, we sign every payload using HMAC SHA-256 and include it in the x-pynath-signature header.
const crypto = require('crypto');
const express = require('express');
const app = express();
// Important: Parse body as raw buffer for signature verification
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-pynath-signature'];
const eventName = req.headers['x-pynath-event'];
const webhookSecret = process.env.PYNATH_WEBHOOK_SECRET;
// 1. Generate our own signature
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(req.body)
.digest('hex');
// 2. Compare signatures
if (signature !== expectedSignature) {
console.error('Invalid signature');
return res.status(401).send('Unauthorized');
}
const payload = JSON.parse(req.body);
// 3. Handle the event
console.log(`Received valid ${eventName} event!`, payload);
// 4. Acknowledge receipt quickly!
res.status(200).send('OK');
});Best Practices
Your server should return a 200 OK status code immediately. If your server takes more than 10 seconds to respond, we will abort the connection and consider the delivery failed. Process heavy logic asynchronously.
Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent.
If your server returns a non-2xx status code or times out, Pynath Booking will automatically retry the webhook delivery using an exponential backoff schedule (up to 5 times over several hours).
Events & Payload Schema
Every webhook payload utilizes a standard envelope that includes metadata about the event. The actual resource data is placed inside the payload object.
Envelope Schema
| Property | Type | Description |
|---|---|---|
| id | string | A unique identifier for the webhook event. Useful for idempotency. |
| event | string | The type of event triggered. One of: booking.created, booking.rescheduled, booking.cancelled. |
| createdAt | timestamp | ISO-8601 UTC timestamp of when the event originally occurred. |
| payload | object | The actual resource data (e.g. the booking object) affected by the event. |
Example Envelope Payload
{
"id": "evt_2X9aB1xyz",
"event": "booking.created",
"createdAt": "2026-07-17T12:00:00Z",
"payload": {
"booking": {
"id": "bk_abc123",
"serviceId": "srv_xyz789",
"startTime": "2026-07-20T10:00:00Z",
"endTime": "2026-07-20T11:00:00Z",
"status": "confirmed",
"customer": {
"id": "cus_987",
"name": "Jane Doe",
"email": "[email protected]"
}
}
}
}