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 3000

3. 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

Respond Quickly (2xx)

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.

Idempotency

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.

Automatic Retries

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

PropertyTypeDescription
idstringA unique identifier for the webhook event. Useful for idempotency.
eventstringThe type of event triggered. One of: booking.created, booking.rescheduled, booking.cancelled.
createdAttimestampISO-8601 UTC timestamp of when the event originally occurred.
payloadobjectThe 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]"
      }
    }
  }
}