Agent skill

ROLLER Webhooks Skill

Receive and authenticate ROLLER webhooks. Use when setting up a ROLLER venue or attraction ticketing webhook endpoint, verifying the x-roller-apikey header, debugging 401s, or handling booking, customer, redemption and payment-link events (body `type` plus `eventType`, e.g. type Booking with eventType Created).

Install this skill

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


ROLLER (venue and attraction ticketing) sends a JSON webhook when something happens, such as a booking being created or cancelled. Webhooks are registered through the REST API. They are not signed: when you register one you supply authentication.apiKey, and ROLLER sends that value as plain text in the x-roller-apikey header of every delivery. Your endpoint authenticates the request by comparing the header with the value you chose.

When to Use This Skill

  • How do I receive ROLLER webhooks?
  • How do I verify the x-roller-apikey header?
  • Does ROLLER sign webhook payloads? (No: it sends a shared API key)
  • How do I handle booking created/cancelled or redemption events (type + eventType)?
  • Why does my ROLLER webhook endpoint return 401?

Setup note

Set the webhook's apiKey to a long random value, then paste the same value here.

"Here" is Hookdeck's ROLLER source, or the ROLLER_WEBHOOK_API_KEY variable in your own handler. The apiKey is user-chosen and the docs' example is just a label, which would be trivially guessable, so generate something like openssl rand -hex 32.

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 verifyRollerRequest(headers, apiKey = process.env.ROLLER_WEBHOOK_API_KEY) {
  if (!apiKey) return false;                  // never accept when unconfigured
  const received = headers['x-roller-apikey']; // whole value, plain text
  return Boolean(received) && safeEqual(received, apiKey);
}
  • Header: x-roller-apikey: <apiKey> (compare the whole value; name is case-insensitive).
  • Return 401 on a missing header or mismatch, 400 for a body that isn't a JSON object, 200 once handled. Acknowledge within 10 seconds.
  • Read the body raw (express.raw({ type: '*/*' })) and authenticate before parsing it.
  • Python: hmac.compare_digest on bytes (encode both sides; str raises on non-ASCII).

A static shared credential authenticates the sender, not the body. There is no signature and no timestamp, so there is no replay protection. Use HTTPS only, and rotate the apiKey (re-register the webhook with a new value) if it leaks.

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

Event Types

The JSON body carries the payload type in type (Booking, Customer, Redemption, BulkExport, SignedWaiver, PaymentLink) and the action in eventType (Created, Updated, Cancelled). ROLLER never sends a dotted name: the examples join the two fields into the keys below. All webhooks are POST.

Event (type.eventType)Sent When
Booking.CreatedA booking is created
Booking.UpdatedA booking is changed
Booking.CancelledA booking is cancelled
Customer.CreatedA guest is created (the guest webhook's type is Customer)
Redemption.CreatedA ticket is redeemed
PaymentLink.CreatedA payment link is created

Environment Variables

ROLLER_WEBHOOK_API_KEY=your-long-random-value   # the apiKey you set on the webhook

Local Development

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

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

Use the printed URL as the webhook url when you register it.

Resources


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →