Table of Contents

Webhook Configuration and Usage

This guide explains how to configure and use webhooks with the PMS Web API to receive real-time notifications about system events.

Table of Contents

Overview

Webhooks provide a way for the PMS system to notify your application about events in real-time, eliminating the need for polling. When a configured event occurs, the system sends an HTTP POST request to your specified endpoint with event data.

What are Webhooks

A webhook is an HTTP callback mechanism that allows the API to push data to your application when specific events occur. Instead of your application continuously polling the API for changes, the API proactively sends notifications to your endpoint.

Benefits

  • Real-time notifications: Receive events immediately as they happen
  • Reduced server load: No need for constant polling
  • Efficient: Only receive data when events actually occur
  • Scalable: Works well with high-volume event streams

Setting Up Webhooks

Prerequisites

  1. Publicly accessible endpoint: Your webhook endpoint must be accessible from the internet
  2. HTTPS support: Webhooks are only delivered over HTTPS for security
  3. Valid SSL certificate: Your endpoint must have a valid SSL certificate
  4. API token: You need a valid API token with webhook configuration permissions

Configuration Steps

1. Prepare Your Webhook Endpoint

Create an HTTP endpoint in your application that can receive POST requests:

[HttpPost]
[Route("webhook/pms-events")]
public async Task<IActionResult> HandleWebhook([FromBody] WebhookPayload payload)
{
    // Process webhook payload
    return Ok();
}

2. Configure Webhook URL

Contact your system administrator to configure your webhook URL. You will need to provide:

  • Webhook URL: The full HTTPS URL of your endpoint
  • Events to subscribe: List of events you want to receive
  • Secret key: Optional secret for webhook signature verification

3. Verify Webhook Endpoint

The system will send a verification request to your endpoint. Your endpoint must:

  • Accept POST requests
  • Return HTTP 200 status code
  • Respond within 5 seconds

Configuration via Admin Panel

  1. Log in to the Admin Panel
  2. Navigate to External ServicesYour Service
  3. Go to Webhook Configuration
  4. Enter your webhook URL
  5. Select events to subscribe
  6. Save configuration

Webhook Events

The following events are available for webhook subscription:

Guest Events

  • guest.created - Triggered when a new guest profile is created
  • guest.updated - Triggered when a guest profile is updated
  • guest.deleted - Triggered when a guest profile is deleted

Reservation Events

  • reservation.created - Triggered when a new reservation is created
  • reservation.updated - Triggered when a reservation is updated
  • reservation.cancelled - Triggered when a reservation is cancelled
  • reservation.checked_in - Triggered when a guest checks in
  • reservation.checked_out - Triggered when a guest checks out

System Events

  • service.token_expired - Triggered when your service token is about to expire
  • service.access_revoked - Triggered when access to an operation is revoked

Webhook Payload

Payload Structure

All webhook payloads follow this structure:

{
  "event": "guest.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "hotelCode": "HOTEL001",
    "reservationId": 12345,
    "guest": {
      "id": "guest-123",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "phone": "+1234567890"
    }
  },
  "metadata": {
    "source": "pms-api",
    "version": "1.0"
  }
}

Payload Fields

Field Type Description
event string Event type identifier
timestamp string ISO 8601 timestamp of when the event occurred
data object Event-specific data payload
metadata object Additional metadata about the event

Event-Specific Payloads

guest.created / guest.updated

{
  "event": "guest.created",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "hotelCode": "HOTEL001",
    "reservationId": 12345,
    "guest": {
      "id": "guest-123",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "phone": "+1234567890",
      "salutation": "Mr",
      "dateOfBirth": "1980-05-15"
    }
  }
}

guest.deleted

{
  "event": "guest.deleted",
  "timestamp": "2024-01-15T10:30:00Z",
  "data": {
    "hotelCode": "HOTEL001",
    "reservationId": 12345,
    "guestId": "guest-123"
  }
}

reservation.checked_in / reservation.checked_out

{
  "event": "reservation.checked_in",
  "timestamp": "2024-01-15T14:00:00Z",
  "data": {
    "hotelCode": "HOTEL001",
    "reservationId": 12345,
    "checkInTime": "2024-01-15T14:00:00Z",
    "roomNumber": "101"
  }
}

Security

Webhook Signature Verification

To ensure webhooks are coming from the PMS API and haven't been tampered with, the system includes a signature in the X-Webhook-Signature header.

Verifying Signatures

The signature is computed using HMAC-SHA256:

public bool VerifyWebhookSignature(string payload, string signature, string secret)
{
    var computedSignature = ComputeHMACSHA256(payload, secret);
    return string.Equals(computedSignature, signature, StringComparison.OrdinalIgnoreCase);
}

Example Implementation

[HttpPost]
[Route("webhook/pms-events")]
public async Task<IActionResult> HandleWebhook(
    [FromBody] WebhookPayload payload,
    [FromHeader(Name = "X-Webhook-Signature")] string signature)
{
    // Get your webhook secret from configuration
    var secret = _configuration["WebhookSecret"];
    
    // Read raw request body
    Request.EnableBuffering();
    Request.Body.Position = 0;
    var reader = new StreamReader(Request.Body);
    var rawBody = await reader.ReadToEndAsync();
    Request.Body.Position = 0;
    
    // Verify signature
    if (!VerifyWebhookSignature(rawBody, signature, secret))
    {
        return Unauthorized("Invalid webhook signature");
    }
    
    // Process webhook
    await ProcessWebhook(payload);
    
    return Ok();
}

Best Practices

  1. Always verify signatures: Never process webhooks without signature verification
  2. Use HTTPS only: Webhooks are only delivered over HTTPS
  3. Store secrets securely: Keep your webhook secret in secure storage
  4. Implement idempotency: Handle duplicate webhook deliveries gracefully
  5. Log all webhooks: Maintain logs for debugging and audit purposes

Testing Webhooks

Using Webhook Testing Tools

You can use tools like webhook.site or ngrok to test webhooks during development:

  1. webhook.site: Provides a temporary webhook URL for testing
  2. ngrok: Creates a secure tunnel to your local development server

Example with ngrok

# Start ngrok tunnel
ngrok http 5000

# Use the provided HTTPS URL as your webhook endpoint
# Example: https://abc123.ngrok.io/webhook/pms-events

Manual Testing

You can manually trigger webhook delivery through the Admin Panel:

  1. Navigate to External ServicesYour Service
  2. Go to Webhook Configuration
  3. Click Test Webhook
  4. Select an event type
  5. Review the webhook delivery status

Troubleshooting

Common Issues

Webhook Not Received

Possible causes:

  • Endpoint is not publicly accessible
  • SSL certificate is invalid or expired
  • Endpoint is returning non-200 status codes
  • Firewall blocking incoming requests

Solutions:

  • Verify endpoint accessibility using online tools
  • Check SSL certificate validity
  • Ensure endpoint returns 200 OK
  • Review firewall rules

Invalid Signature

Possible causes:

  • Incorrect secret key
  • Signature verification logic error
  • Request body modified before verification

Solutions:

  • Verify secret key matches configuration
  • Review signature verification implementation
  • Ensure raw request body is used for verification

Duplicate Webhooks

Possible causes:

  • Network retries
  • System retry mechanism

Solutions:

  • Implement idempotency using event ID or timestamp
  • Use database constraints to prevent duplicate processing

Debugging Tips

  1. Enable logging: Log all incoming webhook requests
  2. Check response times: Ensure your endpoint responds within 5 seconds
  3. Monitor status codes: Track webhook delivery success rates
  4. Review webhook logs: Check Admin Panel for webhook delivery history

Webhook Delivery Status

The Admin Panel provides webhook delivery status information:

  • Success: Webhook delivered successfully (200 OK response)
  • Failed: Webhook delivery failed (non-200 response or timeout)
  • Pending: Webhook queued for delivery
  • Retrying: System is retrying failed delivery

Webhook Retry Policy

The system implements automatic retry for failed webhook deliveries:

  • Initial retry: After 1 minute
  • Second retry: After 5 minutes
  • Third retry: After 15 minutes
  • Final retry: After 1 hour

After all retries are exhausted, the webhook is marked as failed and requires manual intervention.

Rate Limiting

Webhook delivery is subject to rate limiting:

  • Maximum webhooks per minute: 100
  • Maximum webhooks per hour: 1000

If you exceed these limits, webhooks will be queued and delivered when capacity is available.

Best Practices

  1. Idempotent handlers: Design your webhook handlers to be idempotent
  2. Fast response: Respond to webhooks quickly (within 5 seconds)
  3. Async processing: Process webhook data asynchronously if needed
  4. Error handling: Implement comprehensive error handling
  5. Monitoring: Set up monitoring and alerts for webhook failures
  6. Documentation: Document your webhook handling logic

Example Implementation

C# / ASP.NET Core

[ApiController]
[Route("webhook")]
public class WebhookController : ControllerBase
{
    private readonly ILogger<WebhookController> _logger;
    private readonly IConfiguration _configuration;

    public WebhookController(
        ILogger<WebhookController> logger,
        IConfiguration configuration)
    {
        _logger = logger;
        _configuration = configuration;
    }

    [HttpPost("pms-events")]
    public async Task<IActionResult> HandleWebhook(
        [FromBody] WebhookPayload payload,
        [FromHeader(Name = "X-Webhook-Signature")] string signature)
    {
        // Verify signature
        if (!VerifySignature(payload, signature))
        {
            _logger.LogWarning("Invalid webhook signature");
            return Unauthorized();
        }

        // Process webhook based on event type
        switch (payload.Event)
        {
            case "guest.created":
                await HandleGuestCreated(payload.Data);
                break;
            case "guest.updated":
                await HandleGuestUpdated(payload.Data);
                break;
            case "guest.deleted":
                await HandleGuestDeleted(payload.Data);
                break;
            default:
                _logger.LogWarning("Unknown event type: {Event}", payload.Event);
                break;
        }

        return Ok();
    }

    private bool VerifySignature(WebhookPayload payload, string signature)
    {
        var secret = _configuration["WebhookSecret"];
        var payloadJson = JsonSerializer.Serialize(payload);
        var computedSignature = ComputeHMACSHA256(payloadJson, secret);
        return string.Equals(computedSignature, signature, StringComparison.OrdinalIgnoreCase);
    }

    private string ComputeHMACSHA256(string data, string secret)
    {
        using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
        return Convert.ToHexString(hash).ToLowerInvariant();
    }

    private async Task HandleGuestCreated(dynamic data)
    {
        _logger.LogInformation("Guest created: {GuestId}", data.guest.id);
        // Your business logic here
    }
}

Node.js / Express

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/webhook/pms-events', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  
  // Verify signature
  const payload = JSON.stringify(req.body);
  const computedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  if (signature !== computedSignature) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook
  const { event, data } = req.body;
  
  switch (event) {
    case 'guest.created':
      handleGuestCreated(data);
      break;
    case 'guest.updated':
      handleGuestUpdated(data);
      break;
    // ... other events
  }
  
  res.status(200).send('OK');
});

function handleGuestCreated(data) {
  console.log('Guest created:', data.guest.id);
  // Your business logic here
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Support

For webhook-related issues or questions:

  • Contact your system administrator
  • Review webhook delivery logs in Admin Panel
  • Check this documentation for troubleshooting steps