In-store integration
Terminal & Session API
Your till already knows the basket total. BaxPay is just the tender it calls — and the card and the fingerprint never touch your system.
The Session API is how a point-of-sale system — a supermarket ECR, a fuel forecourt controller, a kiosk — hands a sale to a BaxPay terminal and gets a result back. Your system stays the source of truth for the basket. BaxPay owns the money and the authentication.
You call us
Your till opens a session with the amount. BaxPay drives the terminal — prompts, biometric, PIN — and returns the outcome.
Cardholder data stays out
PANs and fingerprints flow between the terminal and BaxPay only. Your system never sees them, so your PCI scope stays small.
Idempotent by design
Every session carries your reference. Retries reconcile to the same sale instead of double-charging.
Authenticating as a connection
In-store calls don't use your API key. They use a connection credential, a bxc_ key provisioned
per till or lane (see connections). Every path below starts with
https://sandbox-api.baxpay.com/v1/integration.
Authorization: Bearer bxc_q7Vd2kP9xR4mT1sN…The connection decides which terminal gets the sale (its terminalId) and the mode: a connection
created with a bx_test_ key only ever opens test sessions.
Open a session
/integration/sessionsIdempotency-Key requiredcurl -X POST https://sandbox-api.baxpay.com/v1/integration/sessions \
-H "Authorization: Bearer $BAXPAY_CONNECTION" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"saleReferenceId": "till-04-receipt-99182",
"amountMinor": 4730,
"currency": "EUR"
}'const res = await fetch('https://sandbox-api.baxpay.com/v1/integration/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.BAXPAY_CONNECTION}`,
'Idempotency-Key': crypto.randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
// Your own receipt id: what you reconcile against later, and what makes retries safe.
saleReferenceId: 'till-04-receipt-99183',
amountMinor: 4730, // €47.30, in cents
currency: 'EUR',
}),
});
const body = await res.json();
if (!res.ok) throw new Error(JSON.stringify(body));
console.log(JSON.stringify(body, null, 2));{
"success": true,
"data": {
"sessionId": "4fba7ed1-…",
"saleReferenceId": "till-04-receipt-99182",
"livemode": false,
"status": "CREATED",
"type": "SALE",
"amountMinor": 4730,
"capturedAmountMinor": null,
"currency": "EUR",
"resultCode": null,
"resultDetail": null,
"authCode": null,
"maskedInstrument": null,
"final": false,
"updatedAt": "2026-07-16T14:02:10.000Z"
}
}| Field | Required | Meaning |
|---|---|---|
saleReferenceId | yes | Your receipt or basket id, 1–128 characters. Opening a second session with the same reference on the same connection returns the existing session, never a second charge |
amountMinor | yes | Positive integer in minor units (4730 = €47.30) |
currency | no | ISO code, default EUR |
type | no | SALE (default) or PREAUTH for fuel |
methodHint | no | A hint for the terminal's first screen |
BaxPay queues the sale for the connection's terminal, which prompts the shopper and collects the authentication factors: the same SCA gate as any other BaxPay payment.
Follow the outcome
/integration/sessions/{id}poll/integration/sessions/by-reference/{saleReferenceId}find it by your own referencecurl https://sandbox-api.baxpay.com/v1/integration/sessions/4fba7ed1-… \
-H "Authorization: Bearer $BAXPAY_CONNECTION"{ "success": true, "data": { "sessionId": "4fba7ed1-…", "status": "CREATED", "final": false } }Poll until final is true, then act on status:
status | Final | Meaning |
|---|---|---|
CREATED | no | Received, waiting for the terminal |
AWAITING_CUSTOMER | no | The terminal is showing the amount |
AUTHENTICATING | no | The shopper is authenticating |
AUTHORIZED | no | Funds reserved (fuel pre-auth); capture next |
CAPTURED | yes | Paid: the only outcome that means money moved |
DECLINED | yes | Refused; resultCode says why (DECLINED, TIMEOUT, …) |
VOIDED | yes | You voided it before capture |
EXPIRED | yes | Nobody completed it within 3 minutes of opening; open a new session |
FAILED | yes | Something broke on our side; open a new session |
If your till crashed or timed out and lost the session id, look it up by your own reference. The lookup only sees sessions opened by the calling connection:
curl https://sandbox-api.baxpay.com/v1/integration/sessions/by-reference/till-04-receipt-99182 \
-H "Authorization: Bearer $BAXPAY_CONNECTION"{ "success": true, "data": { "sessionId": "4fba7ed1-…", "saleReferenceId": "till-04-receipt-99182" } }Keep references URL-safe (no /), or URL-encode them in this path.
Void before capture
The shopper walked away? Void the session. Voiding works until the session is final; a captured,
declined or already voided session answers 409.
/integration/sessions/{id}/voidcurl -X POST https://sandbox-api.baxpay.com/v1/integration/sessions/4fba7ed1-…/void \
-H "Authorization: Bearer $BAXPAY_CONNECTION"{ "success": true, "data": { "sessionId": "4fba7ed1-…", "status": "VOIDED", "resultCode": "ABORTED", "final": true } }There is no refund call on the connection API. A captured in-store payment is refunded from the Business dashboard.
In sandbox, drive a session to CAPTURED, DECLINED or a timeout yourself with
/simulate.
Two topologies
Localyou pollYour controller polls GET /integration/sessions/{id} until final: true. Lowest latency, and it
keeps working through a WAN blip.
Cloudwe call youEach time the session reaches an outcome (authorized, captured, declined, voided), BaxPay POSTs
the session view to the connection's callbackUrl,
signed with its callbackSecret using the same HMAC scheme as webhooks
(X-BaxPay-Signature-V2). The body is the session view itself, without a type envelope.
Who calls what
The till only needs the calls above. POST /integration/terminal/claim-next,
/integration/sessions/{id}/biometric and /integration/sessions/{id}/pin are called by BaxPay
terminals, not by your system.
A connection reaches every session of its merchant in its mode (by id), not only its own. Revoking
one connection cuts that till off, but treat every bxc_ credential as able to read and void any
of your sessions.