REST API

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#

  1. Create an order in your system.
  2. Create a Manza checkout session from your server.
  3. Redirect the customer to the returned url.
  4. Listen for checkout_session.completed.
  5. Fulfill the order after the webhook is received.
Do not fulfill on the success_url redirect
Browser redirects can be interrupted, retried, or opened manually. Use the 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:

  1. 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) and assetlinks.json (Android) on the same domain — one URL works on mobile and desktop, with no broken-redirect failure mode.
  2. 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.
  3. Custom app scheme directly. A URL like myapp://payment-success that 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.txt
"success_url": "https://merchant.example.com/payment-success?session={CHECKOUT_SESSION_ID}"
Schemes rejected for security
The API accepts all three forms in 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#

EndpointScope
Create checkout sessionscheckout_sessions:write
Get checkout sessionscheckout_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:

checkout_session.json
{
  "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"
}
Effective status
The 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).

Amount & expiry limits
Sessions must be for at least 10 MAD and greater than 0; there is no API-side maximum (per-payer KYC-tier caps apply when the card is charged). expires_at defaults to 30 minutes from creation and can be set up to 7 days ahead.
request.sh
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
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}"
app.js
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",
});
app.rb
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"
)
app.py
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",
)
main.go
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",
})
main.rs
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",
}))?;
app.exs
{: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"
  })
app.cr
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"
)
app.php
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',
]);
checkout_session.json
{
  "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_...).

request.sh
curl "https://ma.manza.dev/api/checkout_sessions/cs_01964a3b7c8d70008000deadbeef1234" \
  -H "Authorization: Bearer sk_live_..."
manza
manza checkout-sessions get cs_01964a3b7c8d70008000deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.checkoutSessions.get("cs_01964a3b7c8d70008000deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let session = client.checkout_sessions().get("cs_01964a3b7c8d70008000deadbeef1234")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, session} = Manza.CheckoutSessions.get(client, "cs_01964a3b7c8d70008000deadbeef1234")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

session = client.checkout_sessions.get("cs_01964a3b7c8d70008000deadbeef1234")
app.php
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.

StatusMeaningMerchant action
openThe session can still accept payment.Wait for the customer to pay or redirect them to url again.
processingA payment attempt was submitted; provider confirmation is pending.Do not fulfill yet. Wait for webhook confirmation or poll the session.
clearingPayment 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.
completeThe provider settled the payment.Nothing further — settled_at is set and a checkout_session.settled webhook fired.
expiredThe 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.

checkout_session.completed.json
{
  "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#

ScenarioStatusTypeParam
Missing or invalid API key401authentication_error—
API access not enabled for the entity403forbidden_error—
Missing required checkout session scope403insufficient_scope—
Session not found or belongs to another entity404not_found_error—
Invalid account_id422validation_erroraccount_id
Destination account is not open422validation_erroraccount_id
Amount ≤ 0422validation_erroramount
Amount below 10 MAD minimum422validation_erroramount
Missing required field400invalid_request_erroraccount_id, amount, or success_url
Invalid success_url or cancel_url422validation_errorsuccess_url or cancel_url
expires_at in the past422validation_errorexpires_at
expires_at more than 7 days ahead422validation_errorexpires_at
Invalid metadata type or limits422validation_errormetadata