Skip to main content

Webhooks Customer Guide

How to set up webhooks for new leads

Vitaliy Levit avatar
Written by Vitaliy Levit
Updated this week

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/gondola

  • Integration 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-id for 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 32 or 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:

  1. Verify the signature (see Security below)

  2. Respond quickly with HTTP 200-299 (don't process synchronously)

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

  • data.name: Customer's name

  • data.email: Customer's email address

  • data.phone: Customer's phone number (if provided)

  • data.message: Customer's message/inquiry

  • data.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 signup

    • product_inquiry: Product/tour inquiry

    • product_date_inquiry: Product inquiry with date preferences

    • contact: General contact form

    • manual: Manually created

  • data.referrer: URL where inquiry was submitted

  • data.extra_fields: Additional custom fields from the form

  • data.tags: Tags for categorization

  • company.id: Your company ID

  • timestamp: 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:

  1. 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');
    }
  2. 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 documentId to detect duplicates)


Testing Your Webhook

1. Use webhook.site

  1. Copy your unique URL (e.g., https://webhook.site/abc-123-xyz)

  2. Configure this URL in your Gondola webhook settings

  3. Create a test customer message in Gondola

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

  1. Check webhook is active: Contact support to verify your webhook is active

  2. Check event type: Confirm customer-message.created is configured in your event types

  3. Check URL is accessible: Test with curl https://your-webhook-url

  4. Check logs: Contact support to check Gondola logs for delivery errors

  5. Verify signature handling: Ensure your endpoint is correctly verifying the HMAC signature

Signature Verification Fails

  1. Check secret matches: Verify you're using the correct secret from webhook config

  2. Use raw body: Don't parse JSON before computing signature

  3. Check string concatenation: timestamp + rawBody (no separator)

  4. Check encoding: Use UTF-8 for all strings

Delivery Failures

  1. Respond quickly: Must return HTTP 200-299 within 30 seconds

  2. Check error logs: Look at your server logs for exceptions

  3. Test with webhook.site: Verify Gondola is sending correctly

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

  1. Create a new Zap

  2. Choose Webhooks by Zapier as trigger

  3. Select Catch Raw Hook

  4. Copy the webhook URL

  5. Configure in Gondola with your secret

  6. Test by creating a customer message

  7. Continue building your Zap with the parsed data

Make (Integromat) Integration

  1. Create a new scenario

  2. Add Webhooks β†’ Custom webhook module

  3. Create a new webhook and copy the URL

  4. Configure in Gondola with your secret

  5. Add a Webhook Response module (return status 200)

  6. Add signature verification module (using Router + Tools)

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

Did this answer your question?