Webhooks
Set up real-time webhook notifications for new enquiries.
Webhooks
Webhooks allow you to receive real-time HTTP notifications when events occur in your Hen Hideaways account. Instead of repeatedly polling the API to check for new enquiries, webhooks push updates to your server or integration platform instantly.
Overview
When you subscribe to a webhook, Hen Hideaways will send an HTTP POST request to your specified URL whenever the subscribed event occurs.
Benefits:
- Real-time - Notifications arrive within seconds of the event
- Efficient - No need to poll the API repeatedly
- Scalable - Handle hundreds of enquiries without additional API calls
- Reliable - Automatic retries if delivery fails
Supported Events
Currently supported webhook events:
| Event | Trigger |
|---|---|
enquiry.created | A new guest enquiry is submitted for one of your properties |
More events will be added in the future (e.g., enquiry.updated, booking.confirmed).
Creating a Webhook Subscription
Via Zapier (Recommended)
Zapier handles webhook subscriptions automatically when you set up a "New Enquiry (Instant)" trigger. No manual setup required!
Via API
You can also create webhook subscriptions programmatically:
Endpoint: POST /api/zapier/v1/hooks/enquiries
Request:
curl -X POST "https://www.henhideaways.com/api/zapier/v1/hooks/enquiries" \
-H "Authorization: Bearer hh_owner_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://your-server.com/webhooks/enquiries",
"propertyId": null
}'Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
targetUrl | string (URL) | Yes | Your webhook endpoint that will receive the notifications |
propertyId | string (UUID) | No | Filter to specific property. Use null for all properties |
Response:
{
"id": "subscription-uuid",
"targetUrl": "https://your-server.com/webhooks/enquiries",
"propertyId": null,
"event": "enquiry.created"
}Webhook Payload
When an enquiry.created event occurs, Hen Hideaways sends a POST request to your target URL with the following payload:
{
"event": "enquiry.created",
"enquiry_id": "550e8400-e29b-41d4-a716-446655440000",
"property_id": "660e8400-e29b-41d4-a716-446655440111",
"property_title": "Luxury Hen House in Brighton",
"property_url": "https://www.henhideaways.com/properties/luxury-hen-house-brighton",
"owner_email": "owner@example.com",
"subject": "Weekend Hen Party Booking Enquiry",
"message": "Hi, I'm interested in booking your property for a hen weekend in June. We're a group of 12 friends...",
"status": "sent",
"sent_at": "2026-04-15T14:30:00Z",
"user_name": "Sarah Johnson",
"user_email": "sarah.j@example.com",
"party_details": {
"group_size": 12,
"check_in": "2026-06-15",
"check_out": "2026-06-17",
"occasion": "hen_party"
}
}Payload Fields:
| Field | Type | Description |
|---|---|---|
event | string | Event type (enquiry.created) |
enquiry_id | string (UUID) | Unique identifier for the enquiry |
property_id | string (UUID) | Property that received the enquiry |
property_title | string | Name of the property |
property_url | string | Direct link to the property page |
owner_email | string | Email of the property owner |
subject | string | Enquiry subject line |
message | string | Full enquiry message from the guest |
status | string | Enquiry status (sent, responded, archived) |
sent_at | string (ISO 8601) | When the enquiry was submitted |
user_name | string | Guest's name |
user_email | string | Guest's email address |
party_details | object | Booking details (group size, dates, occasion) |
Handling Webhook Deliveries
Responding to Webhooks
Your webhook endpoint must return a 2xx status code (e.g., 200 OK) within 10 seconds to acknowledge successful receipt.
Example Node.js/Express:
app.post('/webhooks/enquiries', (req, res) => {
const payload = req.body;
console.log('New enquiry received:', payload.enquiry_id);
console.log('Property:', payload.property_title);
console.log('Guest:', payload.user_name, payload.user_email);
// Process the webhook asynchronously
processEnquiry(payload).catch(console.error);
// Respond immediately
res.status(200).json({ received: true });
});Example Python/Flask:
@app.route('/webhooks/enquiries', methods=['POST'])
def handle_enquiry_webhook():
payload = request.json
print(f"New enquiry: {payload['enquiry_id']}")
print(f"Property: {payload['property_title']}")
print(f"Guest: {payload['user_name']} ({payload['user_email']})")
# Process asynchronously
process_enquiry.delay(payload)
# Respond immediately
return jsonify({'received': True}), 200Verifying Webhook Authenticity
To verify a webhook came from Hen Hideaways:
- Check the request originates from a Hen Hideaways IP address
- Validate the
enquiry_idexists by fetching it from the API - Implement a secret token in your webhook URL (e.g.,
https://your-server.com/webhooks/enquiries?secret=your-secret-token)
Example verification:
app.post('/webhooks/enquiries', async (req, res) => {
const secret = req.query.secret;
// Verify secret token
if (secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const payload = req.body;
// Optionally verify the enquiry exists
const enquiry = await fetchEnquiryFromAPI(payload.enquiry_id);
if (!enquiry) {
return res.status(404).json({ error: 'Enquiry not found' });
}
// Process webhook
await processEnquiry(payload);
res.status(200).json({ received: true });
});Retry Logic
If your endpoint doesn't respond with a 2xx status code, Hen Hideaways will automatically retry:
- 1st retry: After 1 minute
- 2nd retry: After 5 minutes
- 3rd retry: After 15 minutes
- 4th retry: After 1 hour
- 5th retry: After 4 hours
After 5 failed attempts, the webhook delivery is abandoned. The subscription remains active for future events.
Monitoring Webhooks
View webhook activity in your Owner Portal:
- Go to Settings > Notifications
- Scroll to Connected Zapier Hooks
- View status, last delivery, and any errors
Status indicators:
- Active - Subscription is working correctly
- Failed - Recent deliveries failed (check your endpoint)
- Inactive - Subscription was manually paused or deleted
Deleting a Webhook Subscription
Via Zapier
Simply turn off or delete the Zap. Zapier will automatically unsubscribe the webhook.
Via API
Endpoint: DELETE /api/zapier/v1/hooks/enquiries
Request:
curl -X DELETE "https://www.henhideaways.com/api/zapier/v1/hooks/enquiries" \
-H "Authorization: Bearer hh_owner_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://your-server.com/webhooks/enquiries",
"propertyId": null
}'Response:
{
"success": true
}Testing Webhooks
Using Zapier
- Set up a Zap with "New Enquiry (Instant)" trigger
- Add a simple action (e.g., send email to yourself)
- Submit a test enquiry via your property page
- Check if the action fired
Using webhook.site
For testing without Zapier:
- Visit webhook.site
- Copy the unique URL
- Create a webhook subscription with that URL
- Submit a test enquiry
- View the webhook payload on webhook.site
Manual Testing
Create a subscription and trigger a webhook:
# 1. Create subscription
curl -X POST "https://www.henhideaways.com/api/zapier/v1/hooks/enquiries" \
-H "Authorization: Bearer hh_owner_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://webhook.site/your-unique-id",
"propertyId": null
}'
# 2. Submit an enquiry via your property page
# 3. Check webhook.site to see the payloadBest Practices
- Respond quickly - Return a
200 OKwithin 10 seconds - Process asynchronously - Don't block the webhook response with slow operations
- Implement idempotency - Handle duplicate deliveries gracefully (use
enquiry_idto deduplicate) - Log everything - Keep detailed logs for debugging
- Monitor failures - Set up alerts for webhook delivery failures
- Use HTTPS - Always use secure webhook URLs
- Validate payloads - Check the data before processing
- Handle errors gracefully - Implement proper error handling
Common Issues
Webhook not firing
- Verify the subscription is active in Owner Portal
- Check your endpoint is publicly accessible (not localhost)
- Ensure HTTPS is properly configured
- Verify firewall isn't blocking requests
Endpoint receiving duplicate webhooks
- This is normal - implement idempotency using
enquiry_id - Store processed IDs in a database/cache
- Skip already-processed enquiries
Webhooks timing out
- Your endpoint must respond within 10 seconds
- Move slow operations (database writes, external API calls) to background jobs
- Return
200 OKimmediately, then process asynchronously
Security Considerations
- Use HTTPS - Never use plain HTTP for webhook URLs
- Implement authentication - Use a secret token in the URL or verify request signatures
- Validate inputs - Don't trust webhook data blindly
- Rate limiting - Implement rate limiting to prevent abuse
- IP whitelisting - Optionally restrict to Hen Hideaways IP addresses
Need Help?
- Review the API Reference for endpoint details
- Check the Getting Started guide for setup instructions
- Contact support for webhook-specific issues
Example: Complete Webhook Handler
Here's a production-ready webhook handler example:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// In-memory set to track processed enquiry IDs (use Redis in production)
const processedEnquiries = new Set();
app.post('/webhooks/enquiries', async (req, res) => {
try {
// 1. Verify secret token
const secret = req.query.secret;
if (secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
// 2. Parse payload
const payload = req.body;
const { enquiry_id, property_title, user_name, user_email } = payload;
// 3. Check for duplicates (idempotency)
if (processedEnquiries.has(enquiry_id)) {
console.log(`Duplicate webhook for enquiry ${enquiry_id}, skipping`);
return res.status(200).json({ received: true, duplicate: true });
}
// 4. Acknowledge receipt immediately
res.status(200).json({ received: true });
// 5. Mark as processed
processedEnquiries.add(enquiry_id);
// 6. Process asynchronously (non-blocking)
processEnquiryAsync(payload);
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
async function processEnquiryAsync(payload) {
try {
// Send notification email
await sendEmail({
to: 'reservations@example.com',
subject: `New Enquiry - ${payload.property_title}`,
body: `
New enquiry from ${payload.user_name} (${payload.user_email})
Property: ${payload.property_title}
Message: ${payload.message}
View in portal: https://owners.henhideaways.com/enquiries
`
});
// Log to database
await db.enquiries.create({
enquiry_id: payload.enquiry_id,
property_id: payload.property_id,
user_email: payload.user_email,
received_at: new Date(),
payload: JSON.stringify(payload)
});
console.log(`Successfully processed enquiry ${payload.enquiry_id}`);
} catch (error) {
console.error(`Failed to process enquiry ${payload.enquiry_id}:`, error);
// Implement retry logic or alert monitoring
}
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});This example demonstrates:
- Secret token verification
- Idempotency handling
- Immediate response
- Asynchronous processing
- Error handling
- Logging
Adapt this pattern to your specific needs!