Jetron TicketFluid

Overview

Get notified on your server when a purchase completes, whichever way you sell.

Webhooks tell your server that something happened in Jetron Ticket. When an attendee completes a purchase, Jetron sends a signed POST to a URL you register, so you can fulfil, record, or notify without polling the API.

Webhooks work the same way for both integration paths. Whether the sale came through the checkout embed or the API, you receive the same event. For the embed, where the parent page never learns that a purchase happened, webhooks are the only way to react on your side.

Registering an endpoint

Add your endpoint in the Jetron Ticket dashboard, on the event's Integrations tab. There is no signing secret to copy: Jetron signs deliveries with your private API key, which you already have.

A few constraints apply:

  • The URL must use HTTPS.
  • Each event may have up to 3 enabled subscriptions.
  • A URL can only be registered once across the platform, so use a distinct path per event if you want one service to receive several, for example /api/webhooks/jetron/my-event.
  • Private, loopback, and link-local addresses are rejected, so you cannot point a subscription at localhost. To develop locally, use a tunnel such as ngrok or Cloudflare Tunnel.

Rotating your private key rotates the signature

Deliveries are signed with your private API key. If you rotate that key, every webhook receiver has to be updated with the new value or signatures will stop verifying.

What a delivery looks like

POST /api/webhooks/jetron HTTP/1.1
Content-Type: application/json
User-Agent: Jetron-Webhook-Dispatcher/1.0
X-Jetron-Signature: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08...

{ "eventType": "TICKET_PURCHASED", "data": { "...": "..." } }

Every body shares one envelope: a discriminating eventType and a data object. See Event types for the payloads, and Verifying signatures before you trust any of it.

Delivery behaviour

Your endpoint should acknowledge quickly with a 2xx and do real work afterwards.

BehaviourDetail
RetriesUp to 5 attempts, with backoff of 1s, 2s, 4s, then 8s
Overall budgetThe whole delivery is capped at about 30 seconds, so in practice expect roughly 3 attempts
Timeout10 seconds per attempt
SuccessAny status from 200 to 399
RedirectsNot followed. A 3xx counts as delivered, so never redirect your webhook URL
OrderingNot guaranteed. Concurrent purchases can arrive out of order
DuplicatesPossible. Retries reuse the same body and signature

Be idempotent

There is no server-side deduplication and no replay protection. Treat data.reference as the idempotency key: if you have already processed that reference, acknowledge the delivery and do nothing else.

Only genuine attendee purchases trigger TICKET_PURCHASED. Complimentary and admin-generated orders do not.

A minimal receiver

app/api/webhooks/jetron/route.ts
export const runtime = 'nodejs' // HMAC needs the Node runtime
export const dynamic = 'force-dynamic'

export async function POST(req: Request) {
  const raw = await req.text() // read the raw body before parsing
  const signature = req.headers.get('x-jetron-signature')

  if (!verifySignature(raw, signature)) {
    return Response.json({ error: 'invalid signature' }, { status: 401 })
  }

  const event = JSON.parse(raw)

  if (event.eventType === 'TICKET_PURCHASED') {
    await fulfil(event.data) // idempotent on event.data.reference
  }

  return Response.json({ ok: true })
}

verifySignature is on the next page. The one thing to get right here is reading the raw body first: parsing and re-serializing changes the bytes and the signature will never match.

Live example

boba.jtr.rsvp/webhooks shows a working receiver with a live feed of verified deliveries, including retry counts. The receiver is open source at jetronmall/boba-event, and Example site explains it.

On this page