- Checkout
- Features
Hosted Payment Page session reference
The /hpp/v1 endpoints — create, read, distribute and disable a hosted session — plus every branding option, session status, callback body and distribution error code.
The Hosted Payment Page API lives at /hpp/v1 and has four operations — create a session,
read it, distribute it, disable it — plus one callback Kustom makes to you. Every
customization of the hosted page is a parameter of the create call, so they are documented
here rather than separately. For the flow these calls sit in, read
Integrating the Hosted Payment Page.
Endpoints at a glance
Section titled “Endpoints at a glance”| Operation | Call | Auth |
|---|---|---|
| Create a session | POST /hpp/v1/sessions | Basic |
| Read a session | GET /hpp/v1/sessions/{session_id} | Basic |
| Distribute a session | POST to the distribution_url from the create response | Basic |
| Disable a session | DELETE /hpp/v1/sessions/{session_id} | Basic |
| Status callback | POST from Kustom to your merchant_urls.status_update | Yours to secure |
Base URLs are https://api.playground.kustom.co and https://api.kustom.co.
Creating a session
Section titled “Creating a session”curl -X POST https://api.kustom.co/hpp/v1/sessions \ --header "Authorization: Basic <token>" \ --header "Content-Type: application/json" \ --data @session.jsonThe order the session wraps
Section titled “The order the session wraps”payment_session_url points at the order the session renders. Build it from the
order_id the Checkout API returned:
https://<base url>/checkout/v3/orders/<order_id>Use the same environment in this URL as the credentials you are calling with.
Merchant URLs
Section titled “Merchant URLs”Where the customer goes after acting on the page, and where Kustom notifies you. All are
optional. Leave success, failure, back, cancel and error empty and Kustom shows
its own generic pages instead.
| Key | Purpose |
|---|---|
success | Redirect after a successful authorization |
failure | Redirect after a refusal |
back | Redirect from the Back button — the session stays reachable |
cancel | Redirect from the Cancel button — the session becomes CANCELLED. Set without back on an order that has no options.acquiring_channel, it drives a Back button instead |
error | Redirect after an error. Without it, errors are indistinguishable from refusals |
status_update | Where Kustom POSTs status callbacks. Must be HTTPS |
distribution_update | Where Kustom POSTs once a link has been sent by e-mail or SMS. Must be HTTPS |
Those seven are the whole object. Anything else you send is accepted and discarded.
A distribution_update callback reports the send, not the customer — it carries
session_id, distribution_id, method (email or sms) and the contact information the
link went to, and it fires once per successful distribution. It follows the same retry
policy as status_update, below.
Two placeholders are substituted into these URLs before the customer is sent to them, and which one resolves depends on the URL:
| Placeholder | Value | Where it resolves |
|---|---|---|
{{session_id}} | The hosted session’s identifier | Every URL in the object |
{{order_id}} | The order identifier | success only |
On a checkout session order_id is always populated, so success can carry it
unconditionally. A placeholder that doesn’t resolve for a given URL is removed rather than
left in place — put {{order_id}} in failure and the customer lands on a URL with an empty
parameter, not a literal {{order_id}} you could notice in a log.
Session options
Section titled “Session options”Everything under options. All optional, all defaulting to something sensible.
background_images
Section titled “background_images”A list of images the page may use as its background. Kustom picks the one that best fits
the customer’s viewport, using the width you declare. Images must be served over
HTTPS — the create call rejects any other url with a 400.
| Field | Type | Meaning |
|---|---|---|
url | String | The image, over HTTPS |
width | Int | The screen width this image works best at |
{ "options": { "background_images": [ { "url": "https://example.com/background.jpg", "width": 1280 }, { "url": "https://example.com/background_small.jpg", "width": 480 } ] }}page_title
Section titled “page_title”The heading on the page and the browser tab’s title. Because you supply it, you are responsible for translating it to the session’s locale — Kustom does not.
Type String. Defaults to Complete your purchase.
{ "options": { "page_title": "Complete your purchase" }}Logo and feature image
Section titled “Logo and feature image”Configured on your merchant account in the Portal, not per session. Where they appear depends on the customer’s viewport, since the page is responsive. Changes take a few minutes to show up, in Playground as well as production.
merchant_urls.back
Section titled “merchant_urls.back”Supplying a back URL is itself a customization: it gives the page a Go back button,
which leaves the session alive. Set back alone and that’s the only exit. Set cancel
too and the page draws a Cancel button beside it, which ends the session for good. With
neither, the customer gets a Cancel button leading to Kustom’s own page.
{ "merchant_urls": { "back": "https://example.com/back?token=<random_uuid>&sid={{session_id}}" }}The create response
Section titled “The create response”| Field | Type | Meaning |
|---|---|---|
session_id | String | The session’s identifier. The one field worth storing |
session_url | URL | Read the session here. Needs your credentials |
distribution_url | URL | Distribute the session here. Needs your credentials |
redirect_url | Public URL | Send the customer here |
qr_code_url | Public URL | A QR code image leading to the same page |
expires_at | Date | When the session dies — never after the order, at most 48 hours from creation, less a stagger of up to 59 minutes |
distribution_module | Object | Token and URLs for the standalone Distribution Module |
Use these URLs as returned rather than reconstructing them. Their shape isn’t a stable contract.
Reading a session
Section titled “Reading a session”curl https://api.kustom.co/hpp/v1/sessions/<session_id> \ --header "Authorization: Basic <token>" \ --header "Content-Type: application/json"Just created, waiting for the customer:
{ "session_id": "39a1c773-bafd-754d-af1f-b30c592f1267", "status": "WAITING", "updated_at": "2019-05-13T14:51:46.288Z", "expires_at": "2019-05-15T13:51:43.507Z"}Completed — you get the order identifier. A session on a Checkout order never carries an
authorization_token, whatever place_order_mode says; that field belongs to sessions
on other payment providers:
{ "session_id": "39a1c773-bafd-754d-af1f-b30c592f1267", "status": "COMPLETED", "order_id": "a1a8f727-2756-6058-bd3c-40069be0994b", "klarna_reference": "X438HG0Q", "updated_at": "2019-05-13T14:54:04.675Z", "expires_at": "2019-05-15T13:51:43.507Z"}klarna_reference is the short human-readable reference on the order, the one that
appears on statements and in support conversations. The field name is what the API
actually returns.
Session statuses
Section titled “Session statuses”updated_at always carries the moment the status last changed.
| Status | Final | What happened |
|---|---|---|
WAITING | No | The session exists, and the customer hasn’t opened it |
IN_PROGRESS | No | The customer opened the page |
COMPLETED | Yes | The payment was authorized. Carries order_id |
FAILED | No | The payment was refused. The customer may retry |
CANCELLED | Yes | The customer confirmed a cancellation. Irreversible |
BACK | No | The customer pressed Back. They may return |
ERROR | No | Something failed during payment. The customer may retry |
DISABLED | Yes | You disabled it with a DELETE. Irreversible |
Only three statuses are final. FAILED, BACK and ERROR all leave the session usable,
so don’t treat them as the end of the purchase.
Status callbacks
Section titled “Status callbacks”Set merchant_urls.status_update when creating the session and Kustom POSTs to it on
every status change. This is cheaper and faster than polling, and is the mechanism to
build on.
Callback body
Section titled “Callback body”| Field | Type | Meaning |
|---|---|---|
event_id | String | Event identifier. De-duplicate on it — you may see the same event twice |
session | Object | The session, in exactly the shape the read endpoint returns |
{ "event_id": "27ba32b0-644b-4b22-94a9-dac503bcae18", "session": { "session_id": "39a1c773-bafd-754d-af1f-b30c592f1267", "status": "COMPLETED", "order_id": "a1a8f727-2756-6058-bd3c-40069be0994b", "klarna_reference": "X438HG0Q", "updated_at": "2019-05-13T14:54:04.675Z", "expires_at": "2019-05-15T13:51:43.507Z" }}Retries
Section titled “Retries”Kustom doesn’t wait on your response before carrying on with the customer. It expects a
2xx within three seconds. If none arrives it retries twice more, three seconds apart
— three attempts in total, each with its own three-second deadline — and then gives up
on that callback.
It gives up on the callback, not on the session. Every status change is dispatched and
retried independently, so a missed IN_PROGRESS doesn’t cost you the COMPLETED that
follows. Still, one status change getting three attempts inside ten seconds is a thin
margin, which is why a polling backstop is worth having.
Securing the endpoint
Section titled “Securing the endpoint”The URL must be HTTPS. Kustom sends no signature or authorization header, so put a secret of your own in the URL and check it on arrival — and treat the callback as a trigger to read the session, not as trusted state:
{ "merchant_urls": { "status_update": "https://example.com/hpp-callback?hppSessionId={{session_id}}&secretToken=7d1cbc3b-b30c-4be2-a8c4-dc76482d7bf6" }}Distributing a session
Section titled “Distributing a session”POST to the distribution_url from the create response. Three methods.
By e-mail. Kustom sends the customer a message, localized to the session’s language, containing the link. If the address matches a customer who has used Kustom before and consented to pre-filling, their details are pre-filled, which measurably shortens the payment.
{ "method": "email", "contact_information": { "email": "user@example.com" }}Kustom validates the address and returns 4xx for a bad one, so put validation in your
own UI too. Delivery is asynchronous and can take minutes. Playground is capped at 100
e-mails per day.
By SMS. Kustom texts the link to a phone number. It validates the number against
phone_country (ISO 3166-1 alpha-2) and answers an invalid one with CONSTRAINT_VIOLATION:
{ "method": "sms", "contact_information": { "phone": "07000212345", "phone_country": "SE" }}SMS is sent only when the order’s purchase country is one of AT, DE, DK, FI, FR, GB, NL, NO or SE. Anywhere else Kustom sends an e-mail instead. A repeat SMS for the same session to the same number within 10 minutes (1 minute in Playground) is answered as a success but not sent.
By token. For a customer who has scanned the QR code on a shared screen. Pass the
access_id the code yielded, unmodified and correctly JSON-escaped:
{ "method": "token", "contact_information": { "access_id": "5a5e8e70-1a96-4651-b3b6-b774012a5bbb" }}Distribution error codes
Section titled “Distribution error codes”| Status | Code | Meaning |
|---|---|---|
| 400 | UNKNOWN | Undefined error. Contact Kustom support |
| 400 | MISSING_CONTACT_DATA | The contact block is incomplete |
| 400 | CONSTRAINT_VIOLATION | The request is malformed |
| 400 | UNSUPPORTED_COUNTRY | The phone number’s country is not valid in this region |
| 400 | BLACKLIST_REFUSED | The recipient is blocked |
| 429 | EMAIL_LIMIT_EXCEEDED | The Playground daily e-mail cap was hit |
| 404 | — | The session no longer exists. No body, so there is no code to branch on |
| 502 | — | Distribution failed. No body either; retry, and escalate if it persists |
Only the 400 and 429 responses carry a body with an error_code. A 404 and a 502 are
bodiless, so for those the status is the whole signal.
Disabling a session
Section titled “Disabling a session”curl -X DELETE https://api.kustom.co/hpp/v1/sessions/<session_id> \ --header "Authorization: Basic <token>"No request body, no response body. The session’s status becomes DISABLED, permanently.
A customer who follows the old link gets an explicit error. A customer who was already on
the page gets a “Page Not Found”.
Related
Section titled “Related”- Integrating the Hosted Payment Page — the flow these calls belong to
- Create and update an order — the order a session wraps
- Checkout API reference — the order’s full schema
- Capture an order — what happens after
COMPLETED
This page is also available as Markdown — append .md to its URL — and is indexed in llms.txt.