Blend: Event Platform

Developer docs

Blend Storefront API

Sell tickets to your Blend events from your own custom-designed website: your theme, your checkout, Blend's backend (payments, tickets, emails, fraud protection). No iframe, no Blend-branded popup.

https://api.blendapp.ai/api
  • Overview
  • Enable it
  • Authentication
  • Endpoints
  • Checkout flow
  • Reserved seating
  • Errors & limits
  • Security

Overview

The Storefront API is additive and lives under /v1/storefront/*. It delegates to the same booking and payment engine Blend's own apps use, so pricing authority, oversold protection, QR tickets, and every confirmation email behave identically.

Your custom site  ──(x-blend-key)──►  /v1/storefront/*  ──►  Blend booking engine
  (themed UI)                          (key + domain +        (pricing, capacity,
                                        per-event opt-in)       tickets, emails)
                                                │
                            paid?  ─────────────┘──►  Stripe / Digital Wallet hosted page
                                                       (card entry is NEVER on your site)

Enable it (host panel)

In the Blend host panel → Settings → Storefront API:

  1. Toggle Public checkout on.
  2. Add your site's domain(s): until you do, every request is rejected (fail-closed).
  3. Copy your publishable key.
  4. Switch on each event you want to sell.
  5. Paste the env snippet into your site and deploy.
NEXT_PUBLIC_BLEND_API_URL=https://api.blendapp.ai/api/v1
NEXT_PUBLIC_BLEND_KEY=pk_live_xxxxxxxxxxxxxxxxxxxx
NEXT_PUBLIC_BLEND_EVENT_IDS=<comma-separated event ids you switched on>

Authentication

Send your publishable key as a header on every request:

x-blend-key: pk_live_xxxxxxxxxxxxxxxxxxxx

A request is accepted only when all are true:

Key present, valid & enabled401 MISSING_KEY / INVALID_KEY
Origin/Referer host is allowlisted403 ORIGIN_NOT_ALLOWED
Event is yours & opted-in404 / 403 EVENT_NOT_PUBLIC

Every response is { success, data?, message?, code? }.

Endpoints

Base https://api.blendapp.ai/api

GET/events/:eventId
Live event + ticket catalogue. :eventId may be an id, shortId, or slug. Render whatever tickets[] returns: new tickets appear automatically; never hardcode prices.
POST/quote
Server-authoritative price breakdown: show the processing fee + total transparently before the buyer pays. The fee is resolved from your Blend fee settings (global / per-event / per-organizer), never hardcoded, and matches exactly what is charged. Read-only: no registration, no hold.
{ "eventId": "abc12345", "selectedTicketId": "t1", "quantity": 2 }
// seated: { "eventId":"abc12345", "seatLabels":["A-1","A-2"], "gaSelections":[] }
// → { "data": { "subtotal": 70, "discount": 0, "fee": 6.1, "total": 76.1, "currency": "USD" } }
POST/register-guest
Create a guest registration. Free tickets confirm instantly; paid tickets return a pending registration (booked only after payment). Send quantity for multiple tickets; for reserved seats send seatLabels + seatHoldToken instead of a ticket id.
{ "eventId": "abc12345", "name": "Jane Austen",
  "email": "jane@x.com", "phone": "+961…",
  "selectedTicketId": "t1", "quantity": 2 }

Pricing is recomputed server-side: a tampered client cannot change what is charged. No host or buyer email fires here for online-paid orders; the host is notified and the ticket is issued only after the payment webhook.

POST/stripe/create-checkout-session
Returns a hosted Stripe URL to redirect the buyer to. Forward the full order: the server re-prices it. For GA send selectedTicketId + quantity + selectedTicketAmount (unit × qty); for seats send seatLabels + seatHoldToken. Omitting quantity charges a single ticket.
{ "eventId": "abc12345", "registrationId": "RG12345",
  "selectedTicketId": "t1", "quantity": 2, "selectedTicketAmount": 70,
  "successUrl": "https://yoursite.com/checkout/success?reg=RG12345&session_id={CHECKOUT_SESSION_ID}",
  "cancelUrl": "https://yoursite.com/checkout" }
// → { "data": { "url": "https://checkout.stripe.com/c/pay/…" } }
POST/whish/create-payment
Digital Wallet hosted payment (USD only). Returns { url } to redirect to. Send amount = the fee-inclusive total (from /quote), selectedTicketId + quantity for GA, or seatLabels + seatHoldToken for seats.
GET/registration/:registrationId
Poll payment status on your themed success page (scoped to your own registrations).
// → { "data": { "status": "confirmed",
//        "payment": { "paymentStatus": "paid", "amount": 50 } } }

The full flow

1. GET  /storefront/events/:id            → render tickets in your theme
2. POST /storefront/register-guest        → { registrationId }
3a. free  → show confirmation (already confirmed)
3b. paid  → POST /storefront/stripe/create-checkout-session → redirect to { url }
4. buyer pays on Blend's hosted page; Blend's signed webhook confirms server-side
5. buyer returns to your successUrl; GET /storefront/registration/:id to display status

Payment is confirmed server-side by Blend's signed Stripe webhook / Digital Wallet callback: the success-page poll is only for display. Poll with a hard timeout; if it hasn't flipped, show “your ticket will arrive by email” (it will).

Reserved seating

For events with a Blend seat map, the simplest integration is to embed Blend's own seat picker as an iframe: it renders the map, holds seats (in-memory with a TTL), and posts the held seats + hold token back to your page. You then thread those into the register / pay calls. No seat-rendering code on your side.

<iframe src="https://www.blendapp.ai/events/<slug>/<shortId>/seatmap?app=storefront"></iframe>

// your page  ──postMessage{source:'blend-host',type:'hello'}──►  iframe
// iframe     ──postMessage{type:'held', seatLabels, holdToken, subtotal}──►  your page
// then: register-guest / create-checkout-session with seatLabels + seatHoldToken

Prefer to build your own picker? The hold engine is also exposed directly (same key + domain + per-event gate):

GET/seating/events/:id/config
Seat map + categories + pricing + hold limits.
GET/seating/events/:id/availability
Live per-seat status (available / held / booked / mine).
POST/seating/events/:id/hold-token
Mint an anonymous hold token.
POST/seating/events/:id/hold
Hold seats under a token. Conflicts return the contested labels.
POST/seating/events/:id/hold/:token/extend
Extend a hold before its TTL lapses.
POST/seating/events/:id/release
Release a hold on abandon.

Seats are held in memory with a timeout, never double-booked, and booked only on the payment webhook. Prices come from the seat's category server-side; unpriced seats are rejected, never sold for $0.

Errors & limits

Every response is { success, data?, message?, code? }. Common codes:

401MISSING_KEY / INVALID_KEYNo key, or a disabled/unknown key.
403ORIGIN_NOT_ALLOWEDRequest origin isn’t on the key’s domain allowlist.
403EVENT_NOT_PUBLICEvent exists but isn’t switched on for the storefront.
404EVENT_NOT_FOUND / NOT_FOUNDNot your event/registration (existence is never disclosed across tenants).
400SEAT_NOT_PRICEDA picked seat’s category has no price: set one in the Seating tab.
409SEAT_CONFLICTA seat was just taken by someone else: re-pick.
400AMOUNT_MISMATCHClient amount ≠ server price (e.g. price changed): refresh and retry.
413-Body over 64 KB.
429-Rate limit: ~120 reads/min and ~30 writes/min per IP; payments ~20/min.

Security model

  • Server-authoritative pricing: amounts are recomputed from the event; client values are validated, never trusted.
  • No card data on your site: hosted Stripe/Digital Wallet only (SAQ-A scope).
  • Scoped keys: a key reaches only its own organizer's opted-in events.
  • Rate limited & input-validated: every field is checked (injection / type-confusion safe).
  • Fail-closed: enabled with no domains ⇒ everything is rejected.
  • Revocable: regenerate the key in the panel to instantly cut off old sites.
Blend: Event Platform for Lebanon & Middle East
DiscoverChristmas in LebanonCommunitiesAboutPartnersPressContactFAQ|TermsPrivacyRefundsCookiesAccessibilityStatus
Blend: Event Platform for Lebanon & Middle East
DiscoverChristmas in LebanonCommunitiesAboutPartnersPressContactFAQ
TermsPrivacyRefundsCookiesAccessibilityStatus