BlendPartner API
blendapp.ai

Getting started

OverviewQuickstartHow the integration worksAuthenticationAny backend

Catalog

Listing eventsEvent detail & ticketsWhich events you see

Checkout

Buyer sessionsInventory & seat holdsReserving an orderTaking the paymentConfirming & settlement

Mobile apps

Native UI (no webview)Embedding the webviewiOS (Swift)Android (Kotlin)React Native

Reference

ErrorsRate limitsTesting & go-live

Embedding the webview

Blend's checkout is a web page that your app shows inside a webview. Your app does not implement seat selection, pricing or ticket delivery. Blend draws all of that. What your app owns is small and precise: fetch a private link from your own backend, show it, and be ready to charge the buyer on your own payment rail when the page asks. This page describes those responsibilities without reference to any one platform; the iOS, Android and React Native pages turn them into code.

What the app does, and does not do

The app has exactly three jobs.

  1. Ask your own backend for a session URL

    Your backend, not the app, calls Blend. The app receives a URL and nothing else, no key, no secret, no buyer record.

  2. Show that URL in a webview

    A webview with JavaScript and DOM storage enabled, cookies that survive navigation, and navigation that stays inside the webview for Blend's hosts.

  3. Register the payment bridge, and charge when asked

    Before the page loads, the app registers a native message handler. When the buyer taps Pay, the page posts a small charge message through it. The app tells its own backend, which charges the buyer and confirms to Blend.

Everything else, reserving inventory, confirming the order, issuing the ticket, happens between your backend and Blend, server-to-server. The app never talks to the Blend API directly and never holds Blend credentials.

Keys live on your backend only

Your ck_live_xxxxxxxx key and cs_live_xxxxxxxx secret arrive by email when your partnership is set up. They belong on your backend and nowhere else. An app binary can be unpacked; a secret inside it is public the moment the app ships. Nothing in the mobile integration needs them.

Where the session URL comes from

Your backend creates a session by telling Blend who is about to buy. Blend answers with a private, single-use link that is valid for 30 minutes.

POST/v1/channel/session

Keyed call: send x-blend-key and x-blend-secret. Full reference on Buyer sessions.

Body parameterTypeDescription
buyerName
required
stringThe buyer's full name.
buyerEmail
required
stringWhere the ticket is emailed.
buyerPhonestringOptional.
Response fieldTypeDescription
successbooleantrue on success.
data.urlstringThe link to load in the webview. Looks like https://blendapp.ai/channel/session?token=<64 hex characters>.
data.expiresAtstring (ISO 8601)When the session stops working. 30 minutes after creation.

A typical shape: the app calls a route on your backend, the name is yours, for example GET /api/blend/session, with the app's normal user authentication. Your backend looks up the signed-in user, calls Blend, and returns only url to the app.

Your backend mints the session (the app never sees this code)
// Runs on YOUR server. Keys come from your secret store, never from the app.
const BLEND_API = 'https://api.blendapp.ai/api/v1';

export async function mintBlendSession(buyer: {
  name: string;
  email: string;
  phone?: string;
}): Promise<{ url: string; expiresAt: string }> {
  const res = await fetch(BLEND_API + '/channel/session', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-blend-key': process.env.BLEND_KEY!,     // ck_live_xxxxxxxx
      'x-blend-secret': process.env.BLEND_SECRET!, // cs_live_xxxxxxxx
    },
    body: JSON.stringify({
      buyerName: buyer.name,
      buyerEmail: buyer.email,
      buyerPhone: buyer.phone,
    }),
  });

  const json = await res.json();
  if (!res.ok || !json.success) {
    throw new Error('Blend session failed: ' + res.status);
  }
  // Return only what the app needs. The app receives { url } and nothing else.
  return { url: json.data.url, expiresAt: json.data.expiresAt };
}

Webview requirements

These apply on every platform. Most are one line of configuration; all of them matter, because the checkout depends on a session cookie and on JavaScript, and a webview with either switched off shows a page that looks fine and cannot buy anything.

RequirementTypeDescription
JavaScript enabledrequiredThe checkout and the payment bridge are JavaScript. Without it the page does not render.
DOM storage enabledrequiredThe page keeps transient checkout state in DOM storage. On Android this is off by default.
Cookies persist across navigationsrequiredBlend sets an httpOnly session cookie scoped to /channel. A webview that drops cookies between page loads loses the session and the buyer is sent back to the start. On Android also call CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true).
Do not clear cookies between screensrequiredThe session lasts 30 minutes. Clearing the cookie store when the checkout screen is pushed or popped ends it early.
Blend links stay inside the webviewrequiredDo not hand navigations to an external browser for blendapp.ai, checkout.blendapp.ai or tickets.blendapp.ai. The external browser has no session cookie and no payment bridge. The simplest rule: any host that is blendapp.ai or ends in .blendapp.ai loads in place.
Ticket links can openrequiredAfter payment the page shows the ticket and links to it on tickets.blendapp.ai. Allow that navigation; it is Blend.
Payment bridge registered before loadrequiredThe page checks for the bridge when it renders the Pay button. A bridge registered after load is not seen. See the bridge.
Back navigationrequiredWire the platform back gesture to webView.canGoBack() / goBack(), and only dismiss the screen when there is nothing to go back to.
Loading state until first paintrecommendedShow a placeholder until the first page finishes loading, so the buyer does not see a white rectangle.
Retry on network failurerecommendedIf the first load fails, offer a retry that requests a fresh session URL from your backend. See session lifecycle for why it must be fresh.
Normal mobile user agentrequiredLeave the webview's default user agent in place. Do not spoof a desktop UA: the seat map is touch-optimised and a desktop UA gets the pointer layout.
RotationsafeThe pages are responsive. Landscape and rotation mid-checkout are fine; do not lock orientation on Blend's account.
Pull-to-refreshsafeReloading the current page is safe. Reloading the original session URL after it has been consumed is not; see below.

The payment bridge

When the buyer taps Pay, Blend's page sends the native host one message. The message has the same shape everywhere: a JSON object with type, orderId, amount and currency.

The charge message
{
  "type": "charge",
  "orderId": "<string>",
  "amount": 42.5,
  "currency": "USD"
}
FieldTypeDescription
typestringAlways "charge". Ignore any other value.
orderIdstringThe order Blend just reserved for this session's buyer. This is the only field your backend acts on.
amountnumberMajor currency units, for display on your payment sheet only. Never the amount you charge.
currencystringISO 4217, upper case. Display only.

How the message reaches native code differs by platform. The name is fixed; the case differs between iOS and Android, and each must be exact.

PlatformTypeDescription
iOS. WKWebViewblendChannelPayRegister a WKScriptMessageHandler under that name on the webview's userContentController. The page calls window.webkit.messageHandlers.blendChannelPay.postMessage({…}) and the handler receives a dictionary. iOS guide.
Android. WebViewBlendChannelPayCall webView.addJavascriptInterface(obj, "BlendChannelPay") where obj has a @JavascriptInterface method named postMessage(String). Android bindings accept primitives only, so the page sends JSON.stringify({…}) and the app parses the string. Requires minSdkVersion 17 or higher. Android guide.
React Native, react-native-webviewBlendChannelPayDefine window.BlendChannelPay with injectedJavaScriptBeforeContentLoaded so it forwards to window.ReactNativeWebView.postMessage, and read it in onMessage. This reuses the Android string contract, so one handler serves iOS and Android. React Native guide.
If no bridge is registered

The page checks for the bridge before it offers the Pay button. If neither name is present, the button shows a visible “cannot take payment here” state instead of a dead button. If you see that state in development, the name is misspelled or the handler was registered after the page loaded. Register it before you load the URL.

Powered byBlend

The trust model

The bridge message is a display hint, never the charge instruction

A webview message handler is origin-blind. It fires for a postMessage call from any script that runs in that webview. Blend's page, but also an injected ad, a compromised dependency, or anything else that ever gets to execute there. Native code cannot tell those apart from the message alone. Your app must never take the amount from the message and charge it.

Your app and backend must, in order:

  1. Origin-check before acting

    Confirm that the webview delivering the message is showing a Blend page. On iOS read message.webView?.url; on Android read webView.url; in React Native read event.nativeEvent.url. If the host is not Blend's, discard the message silently. This does not make the channel authenticated, nothing about a webview bridge can, but it closes the “some other page entirely” case.

  2. Re-fetch the authoritative amount server-to-server, and charge that

    Your app sends orderId to your backend. Your backend asks Blend for that order (keyed, with your secret, see Confirming & settlement) and charges the amount Blend returns. The amount in the message exists so your payment sheet can show a number immediately, before that round-trip completes. Nothing more.

  3. Never share the bridge with non-Blend content

    The controller or interface that carries the handler must belong to a webview that loads only Blend. A webview that also renders ads, marketing pages or any third-party content hands those pages the same ability to post a charge message. Give the checkout its own webview, its own registration, and its own lifecycle.

orderId is the real authorization boundary

An orderId can only name an order that Blend itself just created for this session's buyer. The buyer's identity comes from the session cookie, never from the page. So the worst a forged message can do is point your app at the buyer's own pending order, and the amount for that order comes from Blend, not from the message.

After the charge

Once your rail reports success, the app tells your backend, and your backend confirms to Blend with a signed, server-to-server call. The app does not call Blend, and the app does not need to tell the webview anything.

POST/v1/channel/orders/confirm

The page discovers the outcome on its own. After it posts the charge message it polls Blend for the order's status every 3 seconds for up to 6 minutes, then displays the ticket when the order is paid. Your app does not navigate, reload or inject anything into the webview after charging. Full reference on Confirming & settlement.

Confirm promptly

The buyer is watching a spinner for as long as it takes your backend to confirm. Call confirm the moment your rail reports success; do not batch it.

Session lifecycle

  • A session URL is valid for 30 minutes from creation and is single use. Once loaded, the token in it is consumed and the session continues on the cookie.
  • Reloading the current page (pull-to-refresh, a normal reload) is safe: the cookie carries the session.
  • Reloading the original …/channel/session?token=… URL after it has been consumed is not safe. It will not restore the session. If your retry path needs a URL, ask your backend for a new one.
  • Use expiresAt. If the buyer comes back to the checkout screen after it has passed, mint a fresh session rather than showing a stale page.
  • Create the session when the buyer opens the checkout screen, not when they sign in. A session minted early and shown late is a session that expires mid-purchase.

A checklist for review

For whoever signs off on the integration, the things that matter, in one place.

  • No Blend key or secret appears anywhere in the app binary, its configuration, or its build pipeline.
  • The app calls only your own backend. There is no request to api.blendapp.ai from the app.
  • The charge amount is fetched by your backend from Blend using orderId. The amount in the bridge message is only ever displayed.
  • The bridge handler checks the webview's current host before acting.
  • The checkout webview loads Blend only. Its handler registration is not shared with any other webview.
  • The bridge is registered before the URL is loaded.
  • JavaScript, DOM storage and persistent cookies are on. Cookies are not cleared while the checkout screen is alive.
  • Blend hosts navigate inside the webview. Back navigation goes through the webview first.
  • Retry after failure mints a new session; it does not reload a consumed URL.
  • The user agent is the platform default.

Next

iOS (Swift)A complete WKWebView view controller with the bridge, origin check and charge callback.Android (Kotlin)A complete WebView activity with the JavaScript interface, origin check and charge callback.React NativeA complete component on react-native-webview with the injected bridge.Taking the paymentThe bridge and the trust model in full.Confirming & settlementSign and send the confirm call from your backend.Buyer sessionsThe session endpoint your backend calls.ErrorsError shapes and what to do about each.
Powered byBlend© 2026 Blend · Ticketing & event infrastructure