Skip to content

Webhooks & API ​

Webhooks send BagEvent data to your own systems the moment something happens — a registration, a payment, a check-in. The API lets your systems read an event's attendees and orders whenever they need to. Both live on the Integrations page of the organizer app.

Plan and permission

Webhooks and API keys are on the Business plan and above; on other plans the Integrations page offers the upgrade instead. Within your organization, the person setting them up also needs the OpenAPI & webhooks permission, because an API key can read attendee data.

Webhooks ​

Add a webhook ​

  1. In the organizer app, open Settings → Integrations in the left menu.
  2. Under Webhooks, choose + Add webhook.
  3. Fill in:
    • Name — something you will recognise later, e.g. Salesforce sync.
    • Endpoint URL — the https:// address that should receive the data: your CRM's inbound URL, an automation platform's webhook trigger, or a service you run. BagEvent sends a JSON POST and expects a 2xx response. Plain http:// addresses are refused.
    • Events — tick only the event types this endpoint needs.
    • Scope — All events (every event in your organization, including ones you create later) or Selected events only.
  4. Save. BagEvent shows the webhook's signing secret once. Copy it now and store it with your endpoint's configuration — it cannot be shown again.

Event types ​

GroupEvents
Attendeesattendee.registered, attendee.updated, attendee.cancelled, attendee.checked_in
Ordersorder.created, order.paid, order.refunded, order.cancelled
Invoicesinvoice.issued, invoice.voided
Speakersspeaker.submitted, speaker.accepted, speaker.declined
Eventsevent.published, event.updated

attendee.checked_in is the one most CRMs cannot get any other way: not who registered, but who was actually in the room, and at which check-in point.

What a delivery looks like ​

Every delivery is a POST with a JSON body of the same shape:

json
{
  "event": "attendee.checked_in",
  "timestamp": "2026-09-25T08:14:03Z",
  "data": { "attendee": { "…": "…" }, "event": { "…": "…" } }
}

and these headers:

HeaderContains
X-BagEvent-EventThe event type, e.g. order.paid
X-BagEvent-DeliveryA unique ID for this delivery — use it to ignore a duplicate
X-BagEvent-Signature-V2t=<unix seconds>,v1=<signature> — verify this one
X-BagEvent-SignatureOlder body-only signature, still sent; do not build new checks on it

Verify the signature ​

The signature proves a request came from BagEvent and was not replayed later. For each delivery:

  1. Read t and v1 from X-BagEvent-Signature-V2.
  2. Compute HMAC-SHA256(signing secret, t + "." + raw body) as lower-case hex. Use the raw body bytes, before any JSON parsing.
  3. Compare it with v1 in constant time.
  4. Reject the request if t is more than about five minutes from your server's clock.
js
// Node.js
import crypto from 'node:crypto'

function isFromBagEvent(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')))
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))
}

Test before real traffic ​

On the webhook's card, choose Test →. BagEvent sends a sample payload for the first event type the webhook subscribes to, marked "test": true, and shows whether your endpoint answered with a 2xx, how long it took, and the payload it sent. Test sends do not count against the webhook's health.

Retries, the log, and pausing ​

  • Failed deliveries are retried automatically — up to three attempts in total, 10 seconds and then 60 seconds apart. A redirect (3xx) counts as a failure: publish the final URL.
  • Logs on the webhook's card lists every attempt with the request, the response, and the time taken. Open a failed delivery and choose Retry now → once your endpoint is fixed.
  • After ten failures in a row the webhook is paused automatically and marked Error, so BagEvent stops sending to an endpoint that is not there. Fix the endpoint, then choose Resume from the card's ··· menu. You can pause and resume a webhook yourself the same way.

API keys ​

The API reads data; it does not create registrations or change orders. Most integrations use webhooks to hear about changes as they happen and the API to backfill or reconcile.

Create a key ​

  1. On Settings → Integrations, under API keys, type a label (for example CRM sync) and choose + Create API key.
  2. BagEvent shows the key's secret once. Store it in your integration's secret storage; it cannot be shown again.
  3. To stop a key working, choose Revoke on its card. Anything using it stops immediately.

A key never sees more than the person who created it: if that person's role masks attendee emails or phone numbers, the API returns them masked too. Check-in barcodes are not returned.

Read attendees and orders ​

GET https://api.bagevent.io/api/v1/open/events/{eventId}/attendees
GET https://api.bagevent.io/api/v1/open/events/{eventId}/orders

{eventId} is the number in the organizer app's address bar when you have the event open (…/events/42/…).

Sign each request ​

Send three headers with every request:

HeaderValue
X-Api-KeyThe key shown on its card
X-TimestampThe current time in Unix seconds
X-SignatureHMAC-SHA256(secret, payload) as lower-case hex

The signed payload is six lines joined by \n, with no trailing newline:

v2
<X-Timestamp>
<METHOD, upper case>
<path, without the query string>
<query string exactly as sent, or empty>
<sha256 hex of the request body — for a GET, of an empty body>

The empty-body hash is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

A signature is good for one request: resending the same one is refused, so a retry needs a fresh timestamp and signature. Requests with a timestamp more than five minutes off are refused. Each key is rate-limited, so keep polling well under 300 requests a minute and pace any catch-up after an outage.

BagEvent Help Center