Skip to main content
Hosted checkout is the quickest integration. Your page creates a checkout session for an event, sends the buyer to the Laces page it returns, and gets the buyer back when they finish. Laces handles the form, the waiver and the payment: mobile money (MTN and Airtel) today, card coming soon. You need: an API key with Start hosted checkout (checkout_sessions:write), your site’s origin in Allowed domains and in Return URL domains. See Getting your API keys.
Every key, token and id on this site is a made-up example value. Your real keys are issued to you in the Laces app, under Settings → Developers.

The flow

  1. Your page creates a session: POST /checkout-sessions with the event and your two return URLs. From the browser, use the publishable key. From your server, use a bearer token.
  2. Laces returns { id, url, expires_at }, where url is https://laces.run/checkout/{id}.
  3. Redirect the buyer to url.
  4. On laces.run the buyer sees a registration form for exactly that event, with your prefill filled in and a “Cancel and return to your organization” link. They fill it in and pay.
  5. Cancel sends them to on_cancel_url.
  6. Success shows a confirmation with a “Return to your organization” button and redirects automatically after about 5 seconds to on_success_url, carrying the session id.
  7. Your server verifies the session before fulfilling anything.

What the buyer sees

1

Your page

The buyer clicks Register on your event page.
A running club's event page for Kampala City 10K with a green Register button

A partner's event page with its own Register button. The example site and race are made up.

2

The Laces checkout form

Laces opens the form for exactly that event, labelled with your organization’s name. Anything you sent in prefill is already filled in.
Laces checkout headed 'Checkout for Kampala Road Runners' with a Register and Pay form showing name, phone, gender, age group and email fields

The hosted checkout form, with the buyer's details prefilled.

3

Pay with mobile money

The buyer sees every fee before paying, enters their MTN or Airtel number and taps Pay.
Order summary showing subtotal UGX 50,000, Laces Service Fee UGX 1,950 and total UGX 51,950, above a mobile money number field and a Pay UGX 51,950 button

The payment step. This screenshot was taken in test mode, which is why it shows a sandbox banner; live checkouts don't.

A prompt then arrives on the buyer’s phone, and the page waits while they enter their PIN.
A 'Check your phone' screen with a countdown, asking the buyer to enter their Mobile Money PIN

Illustrative: the waiting screen while the buyer approves the prompt on their phone.

4

Confirmation, then back to you

The buyer gets their order code and ticket, and a Return to button labelled with your organization’s name. Laces sends them back to your on_success_url automatically after about 5 seconds.
Confirmation panel with an order code, a QR code and a 'Return to Kampala Road Runners' button

The confirmation (shown here for a free entry) with its Return to button.

5

Your success page

Your server verifies the session (step 2 below) and shows its own confirmation.
The club's page reading 'Thanks, you're in!' with the order code

The partner's own success page after the return.

1. Create the session

Response 201:
Sessions expire after 24 hours, and each session is for one event. Create one when the buyer clicks, not when the page loads.

Return URL rules

Both URLs must be absolute https URLs, at most 2048 characters, with no username or password, whitespace or backslashes, and their origin must exactly match one of your return URL domains. This is what stops Laces checkout being used as an open redirect. The one exception is local development: http://localhost:<port> and http://127.0.0.1:<port> return URLs work, end to end, when you have registered that exact origin in Return URL domains. See Local development domains.

Getting the session id back

  • Put the literal placeholder {CHECKOUT_SESSION_ID} anywhere in on_success_url, and Laces substitutes the real id: https://shop.example.com/thanks/{CHECKOUT_SESSION_ID}. The URL-encoded form %7BCHECKOUT_SESSION_ID%7D works too, so a URL built with new URL() or encodeURIComponent is fine.
  • With no placeholder, Laces adds session_id=LCSCS-… as a query parameter (replacing any session_id you already had there).
  • on_cancel_url is used as given, with no id added.

2. Verify on your server

Never fulfil an order on the browser’s word alone. A redirect to your success page, or an onSuccess callback in the page, is user experience, not proof of payment. Anyone can type your success URL. Before you mark anything paid, confirm it on your server: read the session with GET /checkout-sessions/{id} and require status: "complete", or act on a signed order.paid webhook.
On your success page’s route, read the session with a bearer token:
Response 200:
order is the most recent order made through the session, so a buyer who retries after a failed payment shows their latest attempt. Expired sessions are deleted automatically some time after they expire, after which a read returns 404 not_found, so verify promptly and keep your own record. Always send client_reference_id (your cart id) when you create the session, and check it on return. That stops a session id from someone else’s purchase being replayed against your buyer’s cart. If you create sessions from your server, also store the returned session id with the cart.
The redirect can be lost: the buyer closes the tab, or their connection drops. Subscribe to the order.paid webhook as well, and match it to your cart with attribution.checkout_session_id. Fulfil on whichever arrives first, idempotently.

Errors

A session is never created for an organization that can’t sell, so a buyer is never sent to a checkout page that can’t take their money. Read payments_enabled from GET /events/{eventId} to decide whether to show the button at all.

Test it

There is one environment, so you test with your live key and a real checkout. See Testing your integration for the full approach.
  1. Create an unlisted test event in the Laces app with a free or low-price ticket.
  2. Add your site’s origin to Allowed domains and Return URL domains: your staging site, or http://localhost:<port> while you develop.
  3. Click Register, complete the checkout, and land on your success URL.
  4. Confirm your server’s session read says complete, and that the order.paid webhook arrived.
  5. Refund the order from the Laces app if you paid for it.