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, each on its own tab: Webhooks and API keys.

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, then the Webhooks tab.
  2. Choose + Add.
  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 (sha256=<hex>), still sent on every delivery; 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, open the API keys tab, then type a label (for example CRM sync) and choose Create.
  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 event's key: the 10-character code in the organizer app's address bar when you have the event open (…/events/Hk7mPq2xYz/…). The event's numeric ID — the event.id in webhook payloads — works too. Sign the path exactly as you send it.

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.

Older keys (v1 signing) ​

Keys created before v2 signing was introduced still sign the old way, which covers neither the query string nor the body. They keep working, and their card shows a Legacy v1 signing chip. To move one to v2:

  1. Update your integration to sign the v2 payload described above.
  2. Prove it on a real request: send it signed the v2 way with the extra header X-Signature-Version: v2. While the key is still on v1, that header checks that one request as v2 — a 200 means your integration is ready.
  3. On the key's card, choose Switch to v2 signing and confirm. After that you can drop the header.

The switch is one-way: once a key is on v2, requests signed the v1 way are refused and the key can't be moved back.