Gondola Webhooks - Customer Guide
What are Webhooks?
Webhooks allow you to receive real-time notifications about events happening in your Gondola account. When an event occurs (like a new customer inquiry), Gondola will automatically send an HTTP POST request to a URL you configure.
Use Cases:
Integrate customer inquiries with your CRM (Salesforce, HubSpot, etc.)
Send notifications to Slack, Discord, or Microsoft Teams
Trigger automated workflows in Zapier, Make, or n8n
Sync data with your custom systems in real-time
Getting Started
Step 1: Create a Webhook Endpoint
You need a publicly accessible HTTPS endpoint that can receive POST requests. This could be:
Your own server:
https://yourserver.com/webhooks/gondolaIntegration platform: Zapier webhook trigger, Make webhook module, n8n webhook node
Serverless function: AWS Lambda, Vercel function, Cloudflare Worker
Test with webhook.site: Use
https://webhook.site/unique-idfor testing
Step 2: Configure Your Webhook in Gondola
Contact customer support at support@gondola.travel to set up your webhook. Our team will work with you to configure:
Required Information:
URL: Your webhook endpoint (must start with
https://)Secret: A random secret key for security (generate with
openssl rand -hex 32or we can generate one for you)Event Types: Which events you want to receive (start with
customer-message.created)
Optional Configuration:
Description: Note what this webhook is for (e.g., "Send to Slack #inquiries channel")
Custom Headers: Add custom headers if your endpoint requires them (e.g.,
{"Authorization": "Bearer token"})
Example Configuration:
{
"url": "https://yourserver.com/webhooks/gondola",
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"eventTypes": ["customer-message.created"],
"isActive": true,
"description": "Send customer inquiries to our CRM",
"customHeaders": {
"X-Custom-API-Key": "your_api_key_here"
}
}Step 3: Implement Your Endpoint
Your endpoint should:
Verify the signature (see Security below)
Respond quickly with HTTP 200-299 (don't process synchronously)
Process the payload asynchronously (queue for background processing)
Events
customer-message.created
Triggered when a new customer inquiry is submitted (contact form, product inquiry, newsletter signup).
Example Payload:
{
"eventType": "customer-message.created",
"documentId": "abc123xyz",
"data": {
"id": 42,
"documentId": "abc123xyz",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"message": "I'm interested in your Alaska tour in July...",
"date": "2025-07-15T00:00:00.000Z",
"endDate": "2025-07-22T00:00:00.000Z",
"status": "new",
"source_type": "product_inquiry",
"referrer": "https://example.com/tours/alaska",
"extra_fields": [
{ "name": "Group size", "value": "4", "type": "integer" },
{ "name": "Dietary restrictions", "value": "Vegetarian", "type": "string" }
],
"tags": "summer,alaska",
"createdAt": "2025-10-28T10:30:00.000Z",
"updatedAt": "2025-10-28T10:30:00.000Z"
},
"company": {
"id": 1
},
"timestamp": "2025-10-28T10:30:00.000Z"
}Fields:
eventType: Type of event (customer-message.created)documentId: Unique identifier for this customer messagedata.name: Customer's namedata.email: Customer's email addressdata.phone: Customer's phone number (if provided)data.message: Customer's message/inquirydata.date: Preferred start date (if provided)data.endDate: Preferred end date (if provided)data.status: Message status (new,read,archived)data.source_type: How the inquiry was submitted:newsletter: Newsletter signupproduct_inquiry: Product/tour inquiryproduct_date_inquiry: Product inquiry with date preferencescontact: General contact formmanual: Manually created
data.referrer: URL where inquiry was submitteddata.extra_fields: Additional custom fields from the formdata.tags: Tags for categorizationcompany.id: Your company IDtimestamp: When the event occurred (ISO 8601)
Security
All webhook requests include cryptographic signatures to verify authenticity and prevent tampering.
Verifying Signatures
Headers You'll Receive:
Content-Type: application/json
X-Webhook-Signature: a1b2c3d4e5f6...
X-Webhook-Timestamp: 2025-10-28T10:30:00.000Z
X-Webhook-Event: customer-message.created
Verification Steps:
Check Timestamp (prevent replay attacks):
const timestamp = request.headers['x-webhook-timestamp'];
const timestampDate = new Date(timestamp);
const now = new Date();
const ageSeconds = (now - timestampDate) / 1000;
if (ageSeconds > 300) { // 5 minutes
throw new Error('Webhook timestamp too old');
}Verify Signature:
const crypto = require('crypto');
const signature = request.headers['x-webhook-signature'];
const timestamp = request.headers['x-webhook-timestamp'];
const rawBody = JSON.stringify(request.body); // Important: use raw body
const secret = 'your_webhook_secret'; // From webhook configuration
// Compute expected signature
const signedPayload = timestamp + rawBody;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Compare signatures (timing-safe)
const signatureValid = crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
if (!signatureValid) {
throw new Error('Invalid webhook signature');
}
Example Implementation (Node.js + Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
// IMPORTANT: Use raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
app.post('/webhooks/gondola', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const eventType = req.headers['x-webhook-event'];
const secret = process.env.GONDOLA_WEBHOOK_SECRET;
// Verify timestamp
const timestampDate = new Date(timestamp);
const ageSeconds = (new Date() - timestampDate) / 1000;
if (ageSeconds > 300) {
return res.status(400).json({ error: 'Timestamp too old' });
}
// Verify signature
const signedPayload = timestamp + req.rawBody;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
if (!crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
)) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Signature valid - respond immediately
res.status(200).json({ received: true });
// Process webhook asynchronously
processWebhook(eventType, req.body).catch(console.error);
});
async function processWebhook(eventType, payload) {
if (eventType === 'customer-message.created') {
// Handle customer inquiry
console.log('New inquiry from:', payload.data.email);
// Example: Send to your CRM
await yourCRM.createLead({
name: payload.data.name,
email: payload.data.email,
phone: payload.data.phone,
message: payload.data.message,
source: 'Gondola Webhook',
});
}
}
app.listen(3000);Example Implementation (Python + Flask)
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
from datetime import datetime, timezone
app = Flask(__name__)
SECRET = 'your_webhook_secret' # From webhook configuration
@app.route('/webhooks/gondola', methods=['POST'])
def gondola_webhook():
signature = request.headers.get('X-Webhook-Signature')
timestamp = request.headers.get('X-Webhook-Timestamp')
event_type = request.headers.get('X-Webhook-Event')
# Verify timestamp
timestamp_dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
age_seconds = (datetime.now(timezone.utc) - timestamp_dt).total_seconds()
if age_seconds > 300:
return jsonify({'error': 'Timestamp too old'}), 400
# Verify signature
raw_body = request.get_data(as_text=True)
signed_payload = timestamp + raw_body
expected_signature = hmac.new(
SECRET.encode('utf-8'),
signed_payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected_signature):
return jsonify({'error': 'Invalid signature'}), 401
# Signature valid - respond immediately
payload = request.get_json()
# Process asynchronously (use Celery, etc.)
process_webhook.delay(event_type, payload)
return jsonify({'received': True}), 200
if __name__ == '__main__':
app.run(port=3000)
Retry Logic
If your endpoint is unreachable or returns an error, Gondola will automatically retry delivery:
6 retry attempts with exponential backoff
Delays: 150ms, 300ms, 600ms, 1.2s, 2.4s, 4.8s
Timeout: 30 seconds per request
Success: HTTP status 200-299
After 6 failures: You'll receive an email notification
Best Practices:
Respond with HTTP 200 quickly (within 30 seconds)
Process webhook payload asynchronously (don't block the request)
Implement idempotency (use
documentIdto detect duplicates)
Testing Your Webhook
1. Use webhook.site
Visit webhook.site
Copy your unique URL (e.g.,
https://webhook.site/abc-123-xyz)Configure this URL in your Gondola webhook settings
Create a test customer message in Gondola
Check webhook.site to see the received request
2. Use ngrok (Local Development)
# Start your local server
node server.js # Listening on localhost:3000
# In another terminal, start ngrok
ngrok http 3000
# Use the ngrok HTTPS URL in your webhook config
# Example: https://a1b2c3.ngrok.io/webhooks/gondola
3. Verify Signature
After receiving a webhook, verify the signature matches:
# Extract from webhook.site or your logs
SIGNATURE="a1b2c3d4e5f6..."
TIMESTAMP="2025-10-28T10:30:00.000Z"
PAYLOAD='{"eventType":"customer-message.created",...}'
SECRET="your_webhook_secret"
# Compute expected signature (macOS/Linux)
echo -n "${TIMESTAMP}${PAYLOAD}" | openssl dgst -sha256 -hmac "$SECRET" -hex
# Should match X-Webhook-Signature header
Troubleshooting
Webhook Not Received
Check webhook is active: Contact support to verify your webhook is active
Check event type: Confirm
customer-message.createdis configured in your event typesCheck URL is accessible: Test with
curl https://your-webhook-urlCheck logs: Contact support to check Gondola logs for delivery errors
Verify signature handling: Ensure your endpoint is correctly verifying the HMAC signature
Signature Verification Fails
Check secret matches: Verify you're using the correct secret from webhook config
Use raw body: Don't parse JSON before computing signature
Check string concatenation:
timestamp + rawBody(no separator)Check encoding: Use UTF-8 for all strings
Delivery Failures
Respond quickly: Must return HTTP 200-299 within 30 seconds
Check error logs: Look at your server logs for exceptions
Test with webhook.site: Verify Gondola is sending correctly
Check SSL certificate: Ensure your HTTPS certificate is valid
Integration Examples
Slack Notification
async function sendToSlack(payload) {
const slackWebhookUrl = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL';
await fetch(slackWebhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `π New Customer Inquiry`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*New inquiry from ${payload.data.name}*\n${payload.data.message}`
}
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Email:*\n${payload.data.email}` },
{ type: 'mrkdwn', text: `*Phone:*\n${payload.data.phone || 'N/A'}` },
{ type: 'mrkdwn', text: `*Type:*\n${payload.data.source_type}` },
{ type: 'mrkdwn', text: `*Date:*\n${payload.data.date || 'Not specified'}` }
]
}
]
})
});
}Zapier Integration
Create a new Zap
Choose Webhooks by Zapier as trigger
Select Catch Raw Hook
Copy the webhook URL
Configure in Gondola with your secret
Test by creating a customer message
Continue building your Zap with the parsed data
Make (Integromat) Integration
Create a new scenario
Add Webhooks β Custom webhook module
Create a new webhook and copy the URL
Configure in Gondola with your secret
Add a Webhook Response module (return status 200)
Add signature verification module (using Router + Tools)
Continue building your scenario
FAQ
Q: Can I have multiple webhooks per company?
A: Yes! You can configure multiple webhook endpoints, each listening to different event types.
Q: What if my endpoint is temporarily down?
A: Gondola will retry 6 times with exponential backoff. If all attempts fail, you'll receive an email notification.
Q: Can I receive events for multiple content types?
A: Currently only customer-message.created is supported. More event types coming soon (bookings, reviews, etc.).
Q: Is the webhook payload always the same?
A: The structure is consistent, but optional fields (like phone, date, extra_fields) may be null or empty depending on what the customer provided.
Q: Can I test webhooks without creating real customer messages?
A: Not yet - this feature is planned. For now, create test customer messages or use the Strapi admin to manually trigger events.
Q: What IP addresses does Gondola send webhooks from?
A: Gondola does not have a fixed IP address for webhook delivery. Instead, we recommend using the HMAC signature verification (described above) to authenticate webhook requests.
Q: Can I disable a webhook temporarily?
A: Yes, contact customer support to temporarily disable your webhook. No events will be sent while inactive.
Q: How do I update my webhook configuration?
A: Contact customer support at support@gondola.travel with your changes (URL, secret, event types, custom headers, etc.).
Support
If you need help with webhooks:
π§ Email: support@gondola.travel