Open Banking collection (merchant)
End customers pay through their bank; funds are credited to the platform’s configured collection account (EUR/GBP). Your merchant account is identified by the API key only. Payers always pass through a StixBNK-hosted checkout before Open Banking (see Hosted checkout).
Introduction
The public API accepts POST requests with a JSON body only. The payee (creditor) account is not chosen by the integrator:
the platform applies the server-validated platform collection IBAN (and GBP details where applicable).
https://stixbnk.com — prepend this host to the paths below (e.g. POST https://stixbnk.com/api/v1/payments/merchant/requests). Use your own host only for local or staging environments.
Quick start
Follow these steps to run a first test payment:
- Create an active merchant API key in the portal (Merchant → API Keys) after KYB is approved.
- Call
POST /api/v1/payments/merchant/requestswith amount, currency, market, reference, and a product label (product_nameorproduct.product_name). - Redirect the payer’s browser to the
checkout_urlfrom the response — a StixBNK-hosted page (order summary, merchant-of-record notice) — then the payer continues to Open Banking (Tink) or, in sandbox, to the test bank UI. - Configure a webhook in Merchant → Webhooks to receive
open_banking.collection.updated.
Prerequisites
- Merchant portal account with KYB approved for the direct merchant (onboarding without a reseller).
- An active API key (
merchant_api_keys).
API authentication
Send your API key in the X-API-Key header. Requests without a key or with an inactive key receive 400; KYB not approved returns 403 (kyb_not_approved).
Content-Type: application/json
X-API-Key: your_merchant_api_key
Create a payment request
Method POST · Path /api/v1/payments/merchant/requests
JSON body — field reference
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Amount to collect (e.g. 45.00). |
currency | string | Yes | EUR or GBP (GBP requires market GB and platform GBP collection details). |
market | string | Yes | Pay Direct market (ISO 3166-1 alpha-2, e.g. FR, IT, GB for GBP). |
reference | string | Yes | Your business reference — must be globally unique across all Open Banking collections and must not collide with an existing platform transaction reference. |
product_name | string | Yes* | Product or line-item label (max 255 characters). Stored with the request and shown in the portal under transaction Line item. *Required unless you send product.product_name instead (see product). |
product | object | No | Alternative to top-level product_name: { "product_name": "Your label" }. Must be a JSON object when present; invalid shapes are rejected. |
description | string | No | Human-readable payment description (defaults if omitted). |
locale | string | No | Tink Link locale (e.g. en_US, fr_FR). If omitted, language may be mapped. |
customer_email | string | No | Must be a valid email when provided. |
customer_first_name | string | No | Payer first name (alias: customer_firstname). |
customer_lastname | string | No | Payer last name. Alias: customer_last_name. |
language | string | No | Short code (e.g. en, fr); used to build a Tink locale when locale is not sent. |
store_name | string | No | Store or sub-brand label (stored for portal / logging; shown on the hosted checkout when provided). |
success_url | string | No | Absolute https URL (or http in dev). Browser redirect when the payment ends in a success terminal state. Alias: urlok. |
failure_url | string | No | Absolute URL for failure / cancel / error terminal outcomes. Alias: urlko. |
The product label is persisted for the portal and API logs; optional customer and store fields are also stored for the portal and request logs. None of these override the creditor account.
{
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-merchant-014",
"product_name": "Example product",
"description": "Test Postman",
"customer_email": "payer@example.com",
"customer_lastname": "Doe",
"customer_first_name": "John",
"language": "en",
"store_name": "Example Store",
"success_url": "https://stixbnk.com/success",
"failure_url": "https://stixbnk.com/failed"
}
curl -sS -X POST "https://stixbnk.com/api/v1/payments/merchant/requests" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_key" \
-d '{
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-merchant-014",
"product_name": "Example product",
"description": "Test Postman",
"customer_email": "payer@example.com",
"customer_lastname": "Doe",
"customer_first_name": "John",
"language": "en",
"store_name": "Example Store",
"success_url": "https://stixbnk.com/success",
"failure_url": "https://stixbnk.com/failed"
}'
Equivalent: "product": { "product_name": "Example product" } at the root (omit top-level product_name when you use this object).
Success response
{
"success": true,
"payment_request_id": "33ec136e91cf4cd7bfc5919d27940f44",
"checkout_url": "https://stixbnk.com/checkout/payment/?pr=33ec136e91cf4cd7bfc5919d27940f44",
"checkout_expires_at": "2026-03-25T14:30:00Z",
"funds_destination": "stixbnk"
}
funds_destination is always stixbnk for the merchant API: funds are collected for StixBNK (platform collection account).
After checkout_expires_at (UTC, ISO8601), or once the payment is in a final state, the checkout URL returns HTTP 410 and cannot be used.
When you send "sandbox": true, the JSON success body also includes "sandbox": true and payment_request_id values start with sb_.
Hosted checkout (StixBNK)
Do not send payers straight from your site to Tink. The API returns an absolute checkout_url on the StixBNK host so the payer sees who they are paying before authorising the bank transfer.
- Live payments:
checkout_urlis{base}/checkout/payment/?pr={payment_request_id}. Two-column hosted checkout (order summary on the left; Open Banking + payer details from your JSON on the right). UI is translated for major EU languages; resolution:?lang=xx/ postedui_lang, then JSONlanguage/locale, thenAccept-Language(sorted byq), thenCF-IPCountry(Cloudflare) mapped to a language, else English. First checkout view storescheckout_payer_ipandcheckout_accept_language. Merchant-of-record and legal text at the bottom. Optional JSON for richer summary: product image (HTTPS URL), subtitle, tax/shipping/subtotal fields,shipping_address— see API doc oncollection.php. - Integration sandbox:
checkout_urlis{base}/checkout/sandbox/?pr=sb_…. Same StixBank Sandbox phone UI as/sandbox/ob_bank.php(reference and amount in the chip, no merchant-of-record text), then Open bank simulator →/sandbox/ob_bank.phpfor demo login and approve/decline. - Paths are fixed; there is no legacy checkout redirect endpoint.
Integration sandbox (no real bank)
Use the same endpoint (POST /api/v1/payments/merchant/requests) with "sandbox": true in the JSON body to test redirects, return URLs, webhooks, and completed / failed transaction states without calling Tink.
The integration sandbox is available for testing in all environments.
- KYB is not required for sandbox requests (so you can test before full approval).
- The response
checkout_urlopens the hosted sandbox checkout (/checkout/sandbox/), then the payer follows Open bank simulator to/sandbox/ob_bank.php(mobile-style test UI). Use the demo loginstixbank_sandbox/stixbank_sandbox(username and password), then choose Main checking (payment authorises →completed) or Decline simulator (bank refusal →failed). - After login, the browser is redirected to the same Pay Direct return URL as production, then optionally to your
success_url/failure_urlwith the usual query parameters. - Linked transactions are stored with
sandbox = 1: they do not post to the ledger or affect wallet balances (same as other sandbox transactions in the platform). - Webhook payloads include
"sandbox": trueindatafor simulator payments.
Example request (sandbox)
Same POST URL and X-API-Key header as production. Add "sandbox": true to the JSON body.
Each test still needs a unique reference (same rules as live).
{
"sandbox": true,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-merchant-sandbox-001",
"product_name": "Sandbox test product",
"description": "Integration test via StixBank Sandbox",
"customer_email": "payer@example.com",
"customer_lastname": "Doe",
"customer_first_name": "John",
"language": "en",
"store_name": "Example Store",
"success_url": "https://stixbnk.com/success",
"failure_url": "https://stixbnk.com/failed"
}
curl -sS -X POST "https://stixbnk.com/api/v1/payments/merchant/requests" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_key" \
-d '{
"sandbox": true,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-merchant-sandbox-001",
"product_name": "Sandbox test product",
"description": "Integration test via StixBank Sandbox",
"customer_email": "payer@example.com",
"customer_lastname": "Doe",
"customer_first_name": "John",
"language": "en",
"store_name": "Example Store",
"success_url": "https://stixbnk.com/success",
"failure_url": "https://stixbnk.com/failed"
}'
Example success response (sandbox)
payment_request_id starts with sb_; checkout_url points to /checkout/sandbox/ on your host, then Open bank simulator leads to /sandbox/ob_bank.php for the fake bank flow.
{
"success": true,
"payment_request_id": "sb_a1b2c3d4e5f678901234567890abcdef12",
"checkout_url": "https://stixbnk.com/checkout/sandbox/?pr=sb_a1b2c3d4e5f678901234567890abcdef12",
"checkout_expires_at": "2026-03-25T14:30:00Z",
"funds_destination": "stixbnk",
"sandbox": true
}
Browser return after payment
After the payer completes hosted checkout and the bank flow (Tink or sandbox), Tink (or the sandbox handler) returns the shopper to the platform-configured Pay Direct return URL (merchant callback). After status sync, the browser may be redirected (302) to your
success_url or failure_url when the payment reaches a terminal state.
Query parameters are appended, including payment_request_id, reference, ob_status, and transaction_id when available.
Outbound webhooks
When the collection or linked transaction is updated (callback, provider sync), the platform sends
open_banking.collection.updated to each active endpoint configured in Merchant → Webhooks.
HTTP request
POST, UTF-8 JSON body (envelope below).Content-Type: application/jsonX-Webhook-Signature: hex HMAC-SHA256 of the exact raw body string using the endpoint secret.X-Webhook-Id: event id (idin the envelope).
Verify the signature on the raw string before parsing JSON to avoid whitespace mismatches.
{
"id": "evt_…",
"type": "open_banking.collection.updated",
"created": 1774451228,
"data": { /* see table below */ }
}
HMAC verification (example)
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $raw, $endpointSecret);
$ok = hash_equals($expected, $sig);
data object reference
| Field | Description |
|---|---|
source | Always open_banking for this event. |
actor_type | merchant for payments created via the merchant API. |
payment_request_id | Payment request id (matches API response). |
reference | Business reference from creation. |
amount / currency | Amount and currency. |
market | Market (may be null). |
collection_status | Platform collection status. |
transfer_status | Raw bank transfer status from the provider (e.g. SENT, COMPLETED), or null. |
transaction_id | Internal transaction id when linked. |
linked_merchant_id | For direct merchants this is typically null (reseller sub-merchants populate this). |
callback_error / callback_error_reason | Callback error details when present. |
transaction_status | Linked transaction status when applicable. |
sandbox | true when the payment was created with "sandbox": true (integration simulator). Omitted for live Tink flows. |
Respond with 2xx promptly; non-success responses may be logged for manual retry according to platform policy.
Common errors
- Invalid or inactive API key — check Merchant → API Keys; key must be
active. 403—kyb_not_approved— complete merchant KYB and wait for approval before calling the payment API (not applied when"sandbox": true).merchant_disabled— account suspended; collection blocked until re-enabled.- Missing or invalid product label —
product_name(orproduct.product_name) is required; empty, too long (>255), or a non-objectproductvalue returns400(invalid_request). - Invalid optional URLs —
success_url/failure_urlmust be valid absolute URLs when provided. 409—duplicate_reference— thereferencemust be globally unique for Open Banking collections and must not match an existing transaction reference.