Checkout Sessions API
Create one-off hosted checkout sessions programmatically. Your server creates a session, redirects the customer to the returned checkout URL, and receives a webhook when payment completes.
When to use checkout sessions#
Checkout sessions are for application-driven checkout flows: an order, cart, invoice, or booking exists in your system, your server creates a matching Manza session, and the customer pays on a hosted Manza page. Use them when you want to attach internal identifiers in metadata and get them back in webhooks, redirect the customer back to your app after payment, and keep card collection and API keys off your frontend.
Reach for payment links instead when you need a shareable link that can be managed, reused, or sent manually outside a checkout flow.
Integration flow#
- Create an order in your system.
- Create a Manza checkout session from your server.
- Redirect the customer to the returned
url. - Listen for
checkout_session.completed. - Fulfill the order after the webhook is received.
checkout_session.completed webhook as the canonical fulfillment signal. The {CHECKOUT_SESSION_ID} token in success_url lets you query the session for immediate status feedback when the customer returns — but keep the webhook as the source of truth for fulfillment.Returning customers to your mobile app#
After a successful payment, the customer briefly sees the hosted confirmation page and is then automatically redirected to the URL configured in success_url. The redirect uses client-side navigation, which routes HTTPS URLs through the browser and custom-scheme URLs through the OS deep-link handler.
If the customer paid inside your mobile app — an in-app browser or a system browser launched from the app — there are three options for where to send them. Listed best to worst:
- Universal Link / App Link (recommended). A plain HTTPS URL on your own domain that the OS routes to your installed app, falling back to the web if it isn't installed. Configure
apple-app-site-association(iOS) andassetlinks.json(Android) on the same domain — one URL works on mobile and desktop, with no broken-redirect failure mode. - Dispatcher page on your own domain. An HTTPS URL that runs JavaScript on load to redirect to your app's custom scheme, falling back to the App Store or Play Store when no scheme handler fires. Useful for a conversion-tracking touchpoint before the OS-level hand-off.
- Custom app scheme directly. A URL like
myapp://payment-successthat the OS hands to your installed app. Works only if the app is installed; in-app browsers (Facebook, Instagram, some mail clients) may silently swallow this with no fallback.
"success_url": "https://merchant.example.com/payment-success?session={CHECKOUT_SESSION_ID}"success_url and cancel_url, but always rejects http, javascript, data, file, vbscript, about, and blob schemes. Deep links can be swallowed by in-app browsers and custom schemes fail when the app isn't installed — always treat the checkout_session.completed webhook, never the redirect arriving, as the canonical fulfillment signal.Required scopes#
| Endpoint | Scope |
|---|---|
| Create checkout sessions | checkout_sessions:write |
| Get checkout sessions | checkout_sessions:read |
Checkout session object#
The session inherits currency_code from the destination account. You are viewing the MAD deployment, so a checkout session looks like this:
{
"id": "cs_01964a3b7c8d70008000deadbeef1234",
"status": "open",
"amount": "1500.00",
"currency_code": "MAD",
"success_url": "https://merchant.example.com/success?session=cs_01964a3b7c8d70008000deadbeef1234",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": {
"order_id": "ORD-123",
"user_id": "usr_456"
},
"description": "Order #123",
"url": "https://ma.manza.dev/checkout/cs_01964a3b7c8d70008000deadbeef1234",
"expires_at": "2026-03-15T09:30:00Z",
"completed_at": null,
"settled_at": null,
"transaction": null,
"created_at": "2026-03-15T09:00:00Z"
}status field is effective status, not raw database status. An expired session with an in-flight card payment can be returned as processing until provider confirmation arrives. A confirmed payment reads clearing until the provider settles, then complete with settled_at set.Create checkout session#
POST /api/checkout_sessions
Required fields: account_id, amount, and success_url. Discover valid account IDs via GET /api/accounts. The account must be open and belong to your entity. {CHECKOUT_SESSION_ID} in success_url is resolved to the session ID before redirect. metadata accepts at most 50 keys (letters, numbers, underscores; values ≤ 500 chars).
expires_at defaults to 30 minutes from creation and can be set up to 7 days ahead.curl -X POST "https://ma.manza.dev/api/checkout_sessions" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"account_id": "01964a3b-0000-7000-8000-ac6000000a01",
"amount": "1500.00",
"success_url": "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": { "order_id": "ORD-123", "user_id": "usr_456" },
"description": "Order #123"
}'manza checkout-sessions create \
--account-id 01964a3b-0000-7000-8000-ac6000000a01 \
--amount 1500.00 \
--success-url "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}"import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.checkoutSessions.create({
account_id: "01964a3b-0000-7000-8000-ac6000000a01",
amount: "1500.00",
success_url: "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
cancel_url: "https://merchant.example.com/cancel",
customer_email: "customer@example.com",
metadata: { order_id: "ORD-123", user_id: "usr_456" },
description: "Order #123",
});require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.checkout_sessions.create(
account_id: "01964a3b-0000-7000-8000-ac6000000a01",
amount: "1500.00",
success_url: "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
cancel_url: "https://merchant.example.com/cancel",
customer_email: "customer@example.com",
metadata: { order_id: "ORD-123", user_id: "usr_456" },
description: "Order #123"
)from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.checkout_sessions.create(
account_id="01964a3b-0000-7000-8000-ac6000000a01",
amount="1500.00",
success_url="https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
cancel_url="https://merchant.example.com/cancel",
customer_email="customer@example.com",
metadata={"order_id": "ORD-123", "user_id": "usr_456"},
description="Order #123",
)import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
session, err := client.CheckoutSessions.Create(ctx, manza.Attributes{
"account_id": "01964a3b-0000-7000-8000-ac6000000a01",
"amount": "1500.00",
"success_url": "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": map[string]any{"order_id": "ORD-123", "user_id": "usr_456"},
"description": "Order #123",
})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let session = client.checkout_sessions().create(&serde_json::json!({
"account_id": "01964a3b-0000-7000-8000-ac6000000a01",
"amount": "1500.00",
"success_url": "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": { "order_id": "ORD-123", "user_id": "usr_456" },
"description": "Order #123",
}))?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, session} =
Manza.CheckoutSessions.create(client, %{
"account_id" => "01964a3b-0000-7000-8000-ac6000000a01",
"amount" => "1500.00",
"success_url" => "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
"cancel_url" => "https://merchant.example.com/cancel",
"customer_email" => "customer@example.com",
"metadata" => %{"order_id" => "ORD-123", "user_id" => "usr_456"},
"description" => "Order #123"
})require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
session = client.checkout_sessions.create(
account_id: "01964a3b-0000-7000-8000-ac6000000a01",
amount: "1500.00",
success_url: "https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}",
cancel_url: "https://merchant.example.com/cancel",
customer_email: "customer@example.com",
metadata: {order_id: "ORD-123", user_id: "usr_456"},
description: "Order #123"
)use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$session = $client->checkoutSessions->create([
'account_id' => '01964a3b-0000-7000-8000-ac6000000a01',
'amount' => '1500.00',
'success_url' => 'https://merchant.example.com/success?session={CHECKOUT_SESSION_ID}',
'cancel_url' => 'https://merchant.example.com/cancel',
'customer_email' => 'customer@example.com',
'metadata' => ['order_id' => 'ORD-123', 'user_id' => 'usr_456'],
'description' => 'Order #123',
]);{
"id": "cs_01964a3b7c8d70008000deadbeef1234",
"status": "open",
"amount": "1500.00",
"currency_code": "MAD",
"success_url": "https://merchant.example.com/success?session=cs_01964a3b7c8d70008000deadbeef1234",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": {
"order_id": "ORD-123",
"user_id": "usr_456"
},
"description": "Order #123",
"url": "https://ma.manza.dev/checkout/cs_01964a3b7c8d70008000deadbeef1234",
"expires_at": "2026-03-15T09:30:00Z",
"completed_at": null,
"settled_at": null,
"transaction": null,
"created_at": "2026-03-15T09:00:00Z"
}Redirect your customer to the returned url.
Get checkout session#
GET /api/checkout_sessions/:id
Returns the current checkout session state. Use the session ID from the id field (cs_...).
curl "https://ma.manza.dev/api/checkout_sessions/cs_01964a3b7c8d70008000deadbeef1234" \
-H "Authorization: Bearer sk_live_..."manza checkout-sessions get cs_01964a3b7c8d70008000deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.checkoutSessions.get("cs_01964a3b7c8d70008000deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
session, err := client.CheckoutSessions.Get(ctx, "cs_01964a3b7c8d70008000deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let session = client.checkout_sessions().get("cs_01964a3b7c8d70008000deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, session} = Manza.CheckoutSessions.get(client, "cs_01964a3b7c8d70008000deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
session = client.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$session = $client->checkoutSessions->get('cs_01964a3b7c8d70008000deadbeef1234');Session lifecycle#
Sessions expire automatically after 30 minutes by default; use expires_at to shorten or extend that window. Checkout sessions are immutable after creation — to change the amount, account, description, metadata, or redirect URLs, create a new session.
| Status | Meaning | Merchant action |
|---|---|---|
open | The session can still accept payment. | Wait for the customer to pay or redirect them to url again. |
processing | A payment attempt was submitted; provider confirmation is pending. | Do not fulfill yet. Wait for webhook confirmation or poll the session. |
clearing | Payment was confirmed and the merchant account credited; provider settlement is pending. | Fulfill the order idempotently — the payment will not be asked for again. settled_at is still null. |
complete | The provider settled the payment. | Nothing further — settled_at is set and a checkout_session.settled webhook fired. |
expired | The session can no longer accept payment. | Create a new checkout session if the customer still needs to pay. |
Webhooks#
When payment completes, a checkout_session.completed webhook fires. The payload includes the session data — metadata and customer_email included. The nested transaction includes the gross amount, actual fee_amount, net amount, card summary, provider reference, and account transaction ID. Use it to mark the order paid and start fulfillment. The session status in this payload may be clearing or complete: fulfill on the event, and do not wait for complete. Handlers must be idempotent: store the session id or your metadata.order_id and ignore duplicate completion events for already-fulfilled orders. A checkout_session.settled webhook follows once the provider settles the funds (settled_at set, status complete). See the Webhooks documentation for signature verification and retry policy.
{
"event": "checkout_session.completed",
"created_at": "2026-03-15T09:05:00Z",
"data": {
"checkout_session": {
"id": "cs_01964a3b7c8d70008000deadbeef1234",
"status": "clearing",
"amount": "1500.00",
"currency_code": "MAD",
"success_url": "https://merchant.example.com/success?session=cs_01964a3b7c8d70008000deadbeef1234",
"cancel_url": "https://merchant.example.com/cancel",
"customer_email": "customer@example.com",
"metadata": {
"order_id": "ORD-123",
"user_id": "usr_456"
},
"description": "Order #123",
"url": "https://ma.manza.dev/checkout/cs_01964a3b7c8d70008000deadbeef1234",
"expires_at": "2026-03-15T09:30:00Z",
"completed_at": "2026-03-15T09:05:00Z",
"settled_at": null,
"transaction": {
"id": "01964a3b-7c8d-7000-8000-deadbeef1234",
"status": "accepted",
"amount": "1500.00",
"fee_amount": "15.0",
"net_amount": "1485.0",
"currency_code": "MAD",
"payment_reference": null,
"provider_reference": "PAY-2026-001",
"card_brand": "Visa",
"card_last_four": "4242",
"account_transaction_id": "01964a3b-7c8d-7000-8000-feedface1234",
"processed_at": "2026-03-15T09:04:58Z",
"created_at": "2026-03-15T09:04:00Z"
},
"created_at": "2026-03-15T09:00:00Z"
}
}
}Error responses#
| Scenario | Status | Type | Param |
|---|---|---|---|
| Missing or invalid API key | 401 | authentication_error | — |
| API access not enabled for the entity | 403 | forbidden_error | — |
| Missing required checkout session scope | 403 | insufficient_scope | — |
| Session not found or belongs to another entity | 404 | not_found_error | — |
| Invalid account_id | 422 | validation_error | account_id |
| Destination account is not open | 422 | validation_error | account_id |
| Amount ≤ 0 | 422 | validation_error | amount |
| Amount below 10 MAD minimum | 422 | validation_error | amount |
| Missing required field | 400 | invalid_request_error | account_id, amount, or success_url |
| Invalid success_url or cancel_url | 422 | validation_error | success_url or cancel_url |
| expires_at in the past | 422 | validation_error | expires_at |
| expires_at more than 7 days ahead | 422 | validation_error | expires_at |
| Invalid metadata type or limits | 422 | validation_error | metadata |