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
- What are Webhooks
- Setting Up Webhooks
- Webhook Events
- Webhook Payload
- Security
- Testing Webhooks
- Troubleshooting
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
- Publicly accessible endpoint: Your webhook endpoint must be accessible from the internet
- HTTPS support: Webhooks are only delivered over HTTPS for security
- Valid SSL certificate: Your endpoint must have a valid SSL certificate
- 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
- Log in to the Admin Panel
- Navigate to External Services → Your Service
- Go to Webhook Configuration
- Enter your webhook URL
- Select events to subscribe
- Save configuration
Webhook Events
The following events are available for webhook subscription:
Guest Events
guest.created- Triggered when a new guest profile is createdguest.updated- Triggered when a guest profile is updatedguest.deleted- Triggered when a guest profile is deleted
Reservation Events
reservation.created- Triggered when a new reservation is createdreservation.updated- Triggered when a reservation is updatedreservation.cancelled- Triggered when a reservation is cancelledreservation.checked_in- Triggered when a guest checks inreservation.checked_out- Triggered when a guest checks out
System Events
service.token_expired- Triggered when your service token is about to expireservice.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
- Always verify signatures: Never process webhooks without signature verification
- Use HTTPS only: Webhooks are only delivered over HTTPS
- Store secrets securely: Keep your webhook secret in secure storage
- Implement idempotency: Handle duplicate webhook deliveries gracefully
- 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:
- webhook.site: Provides a temporary webhook URL for testing
- 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:
- Navigate to External Services → Your Service
- Go to Webhook Configuration
- Click Test Webhook
- Select an event type
- 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
- Enable logging: Log all incoming webhook requests
- Check response times: Ensure your endpoint responds within 5 seconds
- Monitor status codes: Track webhook delivery success rates
- 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
- Idempotent handlers: Design your webhook handlers to be idempotent
- Fast response: Respond to webhooks quickly (within 5 seconds)
- Async processing: Process webhook data asynchronously if needed
- Error handling: Implement comprehensive error handling
- Monitoring: Set up monitoring and alerts for webhook failures
- 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