Webhooks
Webhooks allow you to receive real-time notifications when events happen on your Luma calendar—like when a guest registers, an event is created, or a ticket is purchased. Instead of polling the API, webhooks push data to your server as soon as events occur.
Webhooks are only available for Luma Plus subscribers. Find out more about Luma Plus here.
Creating a Webhook
- Visit the Calendars Home and select your calendar
- Navigate to Settings → Developer
- In the Webhooks section, click Create
- Enter your webhook URL
- Select which action types you want to receive (or choose "All Actions")
- Click Create to save
Available Event Types
Calendar & Audience
- Calendar Event Added - Triggered when any event appears on your calendar, including events managed by the calendar, events syndicated from other calendars, and external events you've added.
- Calendar Person Subscribed - Triggered when someone subscribes to your calendar.
Events
- Event Created - Triggered when a new event is created on your calendar.
- Event Updated - Triggered when event details are modified (title, date, location, etc.). Does not trigger for guest registrations.
- Event Canceled - Triggered when an event is canceled.
Guests & Registrations
- Guest Registered - Triggered when someone registers for an event. This includes re-registrations after a guest cancels and registers again.
- Guest Updated - Triggered when a guest registers and whenever guest information changes (check-in status, approval status, profile updates). Note: This fires for both new registrations and updates to existing guests.
Tickets
- Ticket Registered - Triggered for each individual ticket purchase. If a guest buys 3 tickets, this triggers 3 times, allowing you to track individual ticket sales and create unique experiences per ticket.
Understanding Guest vs Ticket Events
Use Guest Registered when you want to trigger once per person registering—for example, to send a welcome email. Use Ticket Registered when you need to track each individual ticket—for example, if you want to generate a unique QR code or badge for every ticket purchased.
Webhook Payloads
Webhook payloads include a type field indicating the event type and a data object with the relevant information. For detailed payload schemas and field descriptions, see the API documentation.
Managing Webhooks
You can manage your webhooks from Settings → Developer:
- View deliveries - Click on a webhook to see recent delivery attempts and their status (delivered, pending, or failed)
- Pause and resume - Temporarily pause a webhook if you need to stop receiving events, then resume when ready
- Delete - Remove webhooks you no longer need
Retry Behavior
If your webhook endpoint fails to respond or returns an error, Luma will automatically retry delivery:
- Up to 3 retry attempts
- Retries occur with exponential backoff (1 minute, 2 minutes, then 4 minutes)
- If your endpoint returns a 410 Gone status, the webhook will be automatically paused
Verifying Webhook Signatures
Every webhook request includes a cryptographic signature so you can verify it came from Luma. When you create a webhook, Luma generates a secret (starting with whsec_) that you should store securely.
Headers included with every webhook request:
Webhook-Signature— Signature in the formatt=<timestamp>,v1=<signature>Webhook-Id— Unique ID for the webhook eventWebhook-Timestamp— Unix timestamp (seconds) when the webhook was sent
How to verify:
- Extract the timestamp (
t) and signature (v1) from theWebhook-Signatureheader - Construct the signed payload:
{timestamp}.{request_body}(the timestamp, a period, then the raw JSON body) - Compute an HMAC-SHA256 of the signed payload using your webhook secret
- Compare the hex digest to the
v1value from the header using a constant-time comparison
Node.js / Bun example:
import { createHmac, timingSafeEqual } from "crypto";
function verifyWebhookSignature(secret, signatureHeader, body) {
const parts = {};
for (const part of signatureHeader.split(",")) {
const idx = part.indexOf("=");
parts[part.slice(0, idx)] = part.slice(idx + 1);
}
const signedPayload = `${parts["t"]}.${body}`;
const expected = createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
const expectedBuf = Buffer.from(expected);
const actualBuf = Buffer.from(parts["v1"]);
return (
expectedBuf.length === actualBuf.length &&
timingSafeEqual(expectedBuf, actualBuf)
);
}
// In your webhook handler:
const isValid = verifyWebhookSignature(
"whsec_your_secret",
req.headers["webhook-signature"],
rawBody,
);
Python example:
import hashlib
import hmac
def verify_webhook_signature(secret, signature_header, body):
parts = {}
for part in signature_header.split(","):
key, _, value = part.partition("=")
parts[key] = value
signed_payload = f"{parts['t']}.{body}"
expected = hmac.new(
secret.encode(), signed_payload.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Tip: Check the timestamp to prevent replay attacks — reject requests where the timestamp is more than a few minutes old.
Best Practices
- Verify signatures - Always verify the
Webhook-Signatureheader to confirm requests are from Luma - Respond quickly - Your endpoint should respond within 5 seconds
- Return success codes - Return a 2xx status code to indicate successful receipt
- Handle duplicates - In rare cases, you may receive the same event more than once—design your system to handle this gracefully
Troubleshooting
If your webhooks aren't working as expected:
- Check delivery status - View recent deliveries in the webhook panel to see if events are being sent and whether they're succeeding or failing
- Verify your endpoint - Make sure your webhook URL is publicly accessible
- Check your server logs - Look for any errors in how your server is processing incoming webhooks