BillKit/Docs Console
Checkout

Create a Checkout session

Starts a subscription by sending the customer through hosted or embedded Checkout. The session carries the price, the trial and the URLs to return to, and the subscription is created once payment is authorised.

POST/v1/checkout/sessions
Operation create_checkout_session · Scope checkout_sessions:write · Spec snapshot 2026-09-13

Parameters

ParameterDescription
cancel_urlstring (uri)requiredMust be an absolute URL. At most 2083 characters.
price_idstringrequiredNo description in the spec.
success_urlstring (uri)requiredMust be an absolute URL. At most 2083 characters.
countrystring | nulloptionalAt most 2 characters.
coupon_codestring | nulloptionalAt most 64 characters.
customer_emailstring (email) | nulloptionalMust be a valid email address.
customer_idstring | nulloptionalNo description in the spec.
customer_namestring | nulloptionalAt most 255 characters.
metadataobject | nulloptionalAt most 50 keys.
methodenum | nulloptionalOne of creditcard, directdebit, ideal, applepay.
trial_days_overrideinteger | nulloptionalBetween 0 and 180.
ui_modeenumoptionalOne of hosted, embedded. Defaults to "hosted".

Returns

A Checkout session. In hosted mode follow url with a 303. In embedded mode url is null and client_secret is set instead: hand it to the Checkout Element and let it mount in place. cancel_url is required either way.

This is how subscriptions are created. There is no endpoint that creates one directly, because a subscription needs a payment mandate and the mandate is what Checkout collects. The subscription does not exist yet when this call returns; it is created when the customer authorises payment, and checkout.session.completed is your signal to provision the account.

Send an Idempotency-Key header. This endpoint can move money, and a retried create without a key can produce a second session and a second charge.

Test it

Run it with a bk_test_ key, open the url it returns, and choose paid at Mollie’s test-mode status selector. BillKit has no test card numbers of its own; Mollie owns that half, and lists them on its Testing page. Then inspect the events it produced in the console under Developers → Events.

Errors

Status Cause
401 Missing or invalid API key.
403 Key is missing the checkout_sessions:write scope.
422 Validation Error
429 Rate limited. Back off and retry.
Request · Node.js
const session = await client.checkoutSessions.create<{ url: string }>({
  customer_id: "cus_9XKp2vQ1",
  price_id: "price_9XKp2vQ1",
  success_url: "https://acme.example/welcome",
  cancel_url: "https://acme.example/pricing",
  metadata: { workspace: "acme-eu" },
});

return Response.redirect(session.url, 303);
Request · Python
session = client.checkout_sessions.create(
    customer_id="cus_9XKp2vQ1",
    price_id="price_9XKp2vQ1",
    success_url="https://acme.example/welcome",
    cancel_url="https://acme.example/pricing",
    metadata={"workspace": "acme-eu"},
)

return redirect(session["url"], code=303)
Request · PHP
$session = $client->checkoutSessions->create([
    'customer_id' => 'cus_9XKp2vQ1',
    'price_id'    => 'price_9XKp2vQ1',
    'success_url' => 'https://acme.example/welcome',
    'cancel_url'  => 'https://acme.example/pricing',
    'metadata'    => ['workspace' => 'acme-eu'],
]);

header('Location: ' . $session['url'], true, 303);
Request · Laravel
// Checkout implements Responsable, so returning it redirects.
return $request->user()->checkout('price_9XKp2vQ1', [
    'success_url' => route('welcome'),
    'cancel_url'  => route('pricing'),
    'coupon_code' => 'LAUNCH',
]);
Request · React
// Ask your server for an embedded session, then mount the element.
// It authenticates with the session's client_secret; there is no
// publishable key in BillKit.
import { BillKitProvider, CheckoutElement } from "@billkit-eu/react";

<BillKitProvider>
  <CheckoutElement
    clientSecret={clientSecret}
    theme={{ colorPrimary: "#2f6bff", borderRadius: "10px" }}
    onSuccess={({ sessionId }) => router.push(`/welcome?cs=${sessionId}`)}
  />
</BillKitProvider>;
Request · curl
curl -X POST https://api.billkit.eu/v1/checkout/sessions \
  -H "Authorization: Bearer $BILLKIT_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cancel_url":"https://acme.example/welcome","price_id":"price_9XKp2vQ1","success_url":"https://acme.example/welcome"}'
Request body · every field
{
  "cancel_url": "https://acme.example/welcome",
  "country": "string",
  "coupon_code": "string",
  "customer_email": "ada@example.com",
  "customer_id": "cus_9XKp2vQ1",
  "customer_name": "Ada Lovelace",
  "metadata": {
    "key": "value"
  },
  "method": "creditcard",
  "price_id": "price_9XKp2vQ1",
  "success_url": "https://acme.example/welcome",
  "trial_days_override": 0,
  "ui_mode": "hosted"
}
Response · 200 OK
{
  "cancel_url": "https://acme.example/welcome",
  "client_secret": "string",
  "coupon_id": "coup_9XKp2vQ1",
  "created": 1789392000,
  "customer_country_code": "NL",
  "customer_email": "ada@example.com",
  "customer_id": "cus_9XKp2vQ1",
  "expires_at": 1789392000,
  "id": "cs_9XKp2vQ1",
  "livemode": false,
  "metadata": {},
  "method": "string",
  "object": "checkout_session",
  "payment_id": "pay_9XKp2vQ1",
  "price_id": "price_9XKp2vQ1",
  "status": "string",
  "subscription_id": "sub_9XKp2vQ1",
  "success_url": "https://acme.example/welcome",
  "trial_days_override": 0,
  "ui_mode": "string",
  "url": "https://acme.example/welcome"
}