Home Features Pricing Blog Docs
Log in Start for Free
English | Deutsch

Webhooks let your systems react to things happening in your Usetix account — a ticket sold, a refund issued, an event going live — without polling our API. When a subscribed event occurs, Usetix sends a signed POST request to a URL you control.

You manage webhooks from your account dashboard under Settings → Webhooks.

Delivery

Property Value
Method POST
Content type application/json
User-Agent usetix/1.0.0 Webhook
Timeout 7 seconds
Max response size 100 KB

Each event produces one delivery attempt per matching webhook. There are no automatic retries today, so make sure your endpoint is available and responds quickly.

Signing

Every request is signed with HMAC-SHA256 using the webhook’s signing_secret, which is shown in the dashboard when you create the webhook. Two headers are sent:

Header Description
X-Webhook-Signature Hex-encoded HMAC-SHA256 of the raw request body, keyed by the webhook’s signing secret.
X-Webhook-Timestamp ISO 8601 UTC timestamp of the event (stable across any future retries).

Verify in Ruby:

expected = OpenSSL::HMAC.hexdigest("SHA256", signing_secret, request.raw_post)
Rack::Utils.secure_compare(expected, request.headers["X-Webhook-Signature"])

Verify in Node.js:

const expected = crypto
  .createHmac("sha256", signingSecret)
  .update(rawBody)
  .digest("hex");
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.header("X-Webhook-Signature")));

Always compute the signature over the raw request body — not a parsed or re-serialized copy — and use a constant-time comparison.

Auto-deactivation

If a webhook fails for 10 consecutive deliveries over more than 1 hour, Usetix automatically deactivates it. Deliveries stop until you reactivate the webhook from the dashboard. An endpoint returning HTTP 2xx counts as success; anything else (timeout, non-2xx, TLS error, DNS failure) counts as failure.

SSRF protection

Webhook URLs that resolve to private, loopback, link-local, or otherwise non-public IPs are rejected at delivery time. Use public hostnames only.

Subscribable events

Each webhook subscribes to one or more of the following actions:

  • order.paid
  • order.refunded
  • order.cancelled
  • event.published
  • event.unpublished

Payload envelope

Every payload has the same top-level shape. The eventable object varies by action.

{
  "action": "order.paid",
  "created_at": "2026-04-22T12:34:56Z",
  "eventable": { "...": "see below" },
  "account": {
    "name": "Example Promoter",
    "subdomain": "example"
  }
}

Order payloads

Sent for order.paid, order.refunded, and order.cancelled. The action field tells you which transition fired.

{
  "action": "order.paid",
  "created_at": "2026-04-22T12:34:56Z",
  "account": { "name": "Example Promoter", "subdomain": "example" },
  "eventable": {
    "id": "ord_c3a9f4e1",
    "order_code": "7K3Q9D2A",
    "display_number": "7K3Q-9D2A",
    "type": "Order",
    "customer_email": "buyer@example.com",
    "customer_name": "Jane Doe",
    "customer_company": "Acme GmbH",
    "customer_phone": "+49 30 1234567",
    "total_amount": "42.00",
    "net_amount": "35.29",
    "vat_amount": "6.71",
    "vat_rate": "19.0",
    "currency": "EUR",
    "status": "paid",
    "paid_at": "2026-04-22T12:34:50Z",
    "invoice_number": "INV-1-2026-00042",
    "payment_provider": "stripe",
    "payment_id": "pi_3OqXyz2eZvKYlo2C0AbCdEfG",
    "attribution": {
      "utm_source": "google",
      "utm_medium": "cpc",
      "utm_campaign": "spring-launch",
      "utm_term": "concert tickets berlin",
      "utm_content": "ad-variant-a",
      "ref": "partner:radiox"
    },
    "b2b_invoice_requested": true,
    "billing_address": {
      "street": "Musterstr. 1",
      "postal_code": "10115",
      "city": "Berlin",
      "country": "DE",
      "vat_id": "DE123456789"
    },
    "custom_field_answers": [
      {
        "id": 42,
        "label": "Dietary preferences",
        "type": "text",
        "value": "Vegan"
      }
    ],
    "items": [
      {
        "id": "oi_4d2a8b9c",
        "check_in_code": "9M5V2H8C",
        "display_check_in_code": "9M5V-2H8C",
        "ticket_title": "General Admission",
        "event_title": "Spring Showcase",
        "event_slug": "spring-showcase",
        "price": "21.00",
        "custom_field_answers": [
          {
            "id": 43,
            "label": "Attendee name",
            "type": "text",
            "value": "Alice"
          },
          {
            "id": 44,
            "label": "T-shirt size",
            "type": "select",
            "value": "M"
          }
        ]
      }
    ]
  }
}
Field Type Notes
eventable.id string Public order ID. Stable; safe to store as your correlation key.
eventable.order_code string Human-readable order code. Safe to show in customer-facing or staff-facing UI.
eventable.display_number string Formatted order code for display, typically grouped as XXXX-XXXX.
eventable.customer_email string Buyer’s email.
eventable.customer_name string Buyer’s name.
eventable.customer_company string | null Company name the buyer entered at checkout. null when not provided.
eventable.customer_phone string | null Phone number the buyer entered at checkout. null when not provided.
eventable.total_amount string Gross total, decimal encoded as a string (e.g. "42.00") to avoid float precision issues.
eventable.net_amount string Net component of total_amount, computed using the same allocation as the customer invoice so the two match to the cent.
eventable.vat_amount string VAT portion of total_amount (total_amount − net_amount).
eventable.vat_rate string Effective VAT rate applied, as a percentage (e.g. "19.0", "7.7", or "0.0" for VAT-exempt).
eventable.currency string ISO 4217 currency code.
eventable.status string paid, refunded, or cancelled.
eventable.paid_at string | null ISO 8601 UTC. null for non-paid statuses.
eventable.invoice_number string | null Customer invoice number once the invoice has been generated. null for order.cancelled and briefly between payment and invoice generation.
eventable.payment_provider string "stripe" or "paypal" — which provider processed this order.
eventable.payment_id string | null Provider-specific payment reference. For Stripe, the payment intent ID (pi_...) — paste it into the Stripe dashboard to find the charge. For PayPal, the capture ID (or order ID before capture). null until the order is paid.
eventable.attribution object Marketing attribution captured at checkout time. Always present; empty object {} if the buyer never arrived with tracking parameters. See Attribution.
eventable.b2b_invoice_requested boolean true when the buyer asked for a business invoice at checkout.
eventable.billing_address object Always present. Individual fields are null when the buyer did not request a business invoice. See Billing address.
eventable.custom_field_answers array Order-scope custom field answers. Empty array if the event has none or the buyer left them blank. See Custom field answers.
eventable.items[].id string Public ID of the order item (one per ticket).
eventable.items[].check_in_code string Human-readable ticket check-in code. Safe to show to staff and customers.
eventable.items[].display_check_in_code string Formatted check-in code for UI, typically grouped as XXXX-XXXX.
eventable.items[].ticket_title string Title of the ticket type.
eventable.items[].event_title string Title of the event the ticket belongs to.
eventable.items[].event_slug string | null URL slug of the event (e.g. "spring-showcase"). Combine with your account’s shop URL to build a deep link back to the public event page. null if the event has been deleted since the order was placed.
eventable.items[].price string Decimal as string.
eventable.items[].custom_field_answers array Attendee-scope custom field answers for this ticket.

Billing address

Populated when the buyer requested a B2B invoice at checkout (b2b_invoice_requested: true). For B2C orders the object is still present but every field is null.

Field Type Notes
street string | null Street and house number.
postal_code string | null Postal / ZIP code.
city string | null City.
country string | null ISO 3166-1 alpha-2 country code (e.g. "DE", "CH", "AT").
vat_id string | null EU VAT ID when supplied. Validated at checkout against the VIES service for cross-border EU orders.

Attribution

The attribution object captures where the buyer came from. UTM parameters and ref are read from the checkout page’s URL — and as a fallback the previous page’s URL via the Referer header — at the moment the buyer submits the order. There is no client-side persistence and no consent banner: the data only travels with the form submission itself. Practically this means last-click attribution — the source that delivered the buyer to the checkout page is what’s recorded.

Field Type Notes
utm_source string | omitted e.g. "google", "facebook", "newsletter".
utm_medium string | omitted e.g. "cpc", "email", "social".
utm_campaign string | omitted Campaign name as set in the URL.
utm_term string | omitted Paid keyword, when supplied.
utm_content string | omitted Ad / creative variant, when supplied.
ref string | omitted Free-form referral code. Usetix’s own shop footer links append ref=shop:<subdomain> when a buyer clicks through to the marketing site, so signups originating from a specific shop carry that source.

Only fields that were actually captured are included. Buyers who arrive with no tracking parameters get "attribution": {}.

Custom field answers

Organizers can define custom checkout questions per event (e.g. dietary preferences, attendee names, t-shirt sizes). Each answer is emitted as:

Field Type Notes
id integer Stable numeric ID of the custom field. Use this as your key — it survives label renames.
label string Current human-readable question label, as the buyer saw it.
type string text, textarea, select, or checkbox.
value string | boolean text, textarea, and select answers arrive as strings. checkbox answers arrive as true or false.

Answers are scoped two ways:

  • Order-scope answers appear once on the order (eventable.custom_field_answers). Used for per-checkout questions like “Dietary preferences” that apply to the whole party.
  • Attendee-scope answers appear per item (eventable.items[].custom_field_answers). Used for per-ticket questions like attendee name or t-shirt size — each ticket in the order carries its own answer.

Blank answers are omitted, so an empty array means the buyer provided no answers (or the event defines no questions for that scope).

If an organizer deletes a custom field after orders were placed, the stored answers remain in Usetix but are no longer emitted on subsequent webhook deliveries for existing orders. Key your integration off id, and treat the absence of a once-seen id as “the question was removed” rather than “the answer was cleared”.

Event payloads

Sent for event.published and event.unpublished.

{
  "action": "event.published",
  "created_at": "2026-04-22T12:34:56Z",
  "account": { "name": "Example Promoter", "subdomain": "example" },
  "eventable": {
    "slug": "spring-showcase",
    "type": "Event",
    "title": "Spring Showcase",
    "starts_at": "2026-05-01T19:00:00Z",
    "ends_at": "2026-05-01T23:00:00Z",
    "venue": {
      "name": "The Venue",
      "city": "Berlin"
    }
  }
}
Field Type Notes
eventable.slug string URL slug. The event’s public URL is https://<subdomain>.usetix.io/events/<slug>.
eventable.title string Event title.
eventable.starts_at string ISO 8601 UTC.
eventable.ends_at string ISO 8601 UTC.
eventable.venue.name string Venue name.
eventable.venue.city string Venue city.

Testing locally

For local development, tools like ngrok or Cloudflare Tunnel give you a public URL that forwards to localhost. Point a webhook at that URL, then trigger actions in your account to see real payloads arrive.