Agent skill

Ventrata Webhooks Skill

Receive and authenticate Ventrata OCTO webhooks. Use when setting up a Ventrata webhook endpoint, checking the secret header you set when creating the webhook, debugging 401s, or handling booking_update, order_update, availability_update and product_update events.

Install this skill

npx skills add hookdeck/webhook-skills --skill ventrata-webhooks


Ventrata (tours and attractions ticketing) delivers OCTO webhooks when orders, bookings, availability or products change. You create the webhook through the OCTO API with the octo/webhooks capability, and Ventrata POSTs a JSON payload to your URL.

Ventrata webhooks are not signed. Add a secret header when you create the webhook through the OCTO API and enter it here.

There is no HMAC and no timestamp. You authenticate requests with a secret header that you choose when you create the webhook (the headers map). Ventrata sends it on every delivery, and your endpoint compares it with the value you stored.

When to Use This Skill

  • How do I receive Ventrata webhooks?
  • Does Ventrata sign webhooks? (No: use a secret header from the webhook's headers map)
  • How do I authenticate a Ventrata OCTO webhook request?
  • How do I handle booking_update, order_update, availability_update or product_update?
  • Why does my Ventrata webhook endpoint return 401?

Verification (core)

const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyVentrataRequest(headers, secret = process.env.VENTRATA_WEBHOOK_SECRET,
    headerName = process.env.VENTRATA_WEBHOOK_HEADER || 'X-Webhook-Secret') {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[headerName.toLowerCase()]; // node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}
  • The header name is whatever you put in headers when creating the webhook. Read it from VENTRATA_WEBHOOK_HEADER (default X-Webhook-Secret) and look it up case-insensitively.
  • VENTRATA_WEBHOOK_SECRET is the exact, whole header value, including any Bearer prefix you typed. Compare the whole value; do not strip or parse a prefix.
  • Python uses hmac.compare_digest on bytes (encode both sides; passing str raises on non-ASCII).
  • Missing header or mismatch: 401. A body that isn't a JSON object: 400. Handled: 200.
  • Read the body raw (express.raw({ type: '*/*' })) so any content type works, and parse it only after the check.
  • A static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Use HTTPS, and rotate the credential if it leaks.

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Event Types

The body is {event, webhook, ...} plus the objects the event concerns: order, booking, product, availability, supplier and diff (the last for order, booking and product updates). The capability documents these four events, while the payload examples write booking.updated (the docs show no dotted form for the other three). Handlers should accept both spellings and dispatch on event.

EventSent When
order_updateAn order changes
booking_update (payload examples: booking.updated)A booking changes
availability_updateAvailability changes
product_updateA product changes

Delivery is always an HTTP POST. Retries are per webhook (retryOnError).

Environment Variables

VENTRATA_WEBHOOK_HEADER=X-Webhook-Secret   # the header name you chose in the webhook's headers map
VENTRATA_WEBHOOK_SECRET=your-secret-value  # the exact, whole header value (including any "Bearer " prefix)

Local Development

Ventrata can't reach localhost. Tunnel with the Hookdeck CLI (no account required):

npx hookdeck-cli listen 3000 ventrata --path /webhooks/ventrata

Use the printed URL as the webhook url when you create the webhook.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 11, 2026

View on GitHub →