Warming up the decks…
Warming up the decks…
BlendPartner APIblendapp.aiBuild it in this order and every step is verifiable before you start the next one. You can get through steps 1 and 2 with nothing but a terminal.
You need a channel key, a channel secret, and a signing key. Blend sends these by email; they are never published in this documentation. Keep the secret and the signing key on your servers.
The catalog endpoint is the cheapest thing to call and needs nothing but your key pair. If this returns events, your credentials, your IP allowlist and your catalog are all correct.
curl https://api.blendapp.ai/api/v1/channel/events \
-H "x-blend-key: ck_live_xxxxxxxxxxxxxxxx" \
-H "x-blend-secret: cs_live_xxxxxxxxxxxxxxxx"An empty list is a valid answer, it means no events have been added to your channel yet, not that anything is broken. A 401 means the key pair is wrong; a 403 means your IP is not on the allowlist.
A session is a short-lived, single-use URL tied to one buyer. Your backend mints it; your app opens it.
curl -X POST https://api.blendapp.ai/api/v1/channel/session \
-H "x-blend-key: ck_live_xxxxxxxxxxxxxxxx" \
-H "x-blend-secret: cs_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"buyerName":"Rami Damianos","buyerEmail":"rami@example.com"}'Mint one when the user opens your events tab, not at app launch. Do not cache the URL across users, it carries that buyer’s identity.
Load the URL. Blend renders the catalog, event pages, seat maps and checkout. There is nothing to build here beyond hosting the webview and registering the payment bridge in the next step.
When the buyer taps Pay, Blend reserves the order and hands your native code an order id and an amount to display. Register the handler before you load the URL, or the Pay button will correctly report that it has nowhere to send the charge.
{
"type": "charge",
"orderId": "6a9b1c.e42",
"amount": 42.5,
"currency": "USD"
}Treat the payload as a display hint only. Before charging, have your backend read the authoritative total for that orderId from Blend and charge that number. Taking the payment explains why in one short section.
Charge the buyer on your own rail. When it succeeds, your backend tells Blend, signed, server to server. Blend issues the ticket, and the webview, which has been polling, shows it to the buyer within a few seconds.
import crypto from "node:crypto";
const orderId = "6a9b1c.e42";
const amount = 42.5;
const partnerReference = "toters_charge_88213"; // your own charge id
const signature = crypto
.createHmac("sha256", process.env.BLEND_SIGNING_KEY)
.update(`${orderId}.${amount.toFixed(2)}.USD.${partnerReference}`)
.digest("hex");
const res = await fetch(
"https://api.blendapp.ai/api/v1/channel/orders/confirm",
{
method: "POST",
headers: {
"x-blend-key": process.env.BLEND_KEY,
"x-blend-secret": process.env.BLEND_SECRET,
"x-blend-signature": signature,
"Content-Type": "application/json",
},
body: JSON.stringify({ orderId, amount, partnerReference }),
},
);If you get 503 ACCRUAL_FAILED_RETRY, retry, it is safe. If you get 409 CONFIRM_FAILED with refundRequired, refund the buyer: no ticket was issued.
Never guess. Ask: POST /v1/channel/orders/status with the order id returns paid, pending or failed. Confirm is idempotent, so retrying is always safer than assuming.