Open Banking collection (reseller)
End customers pay through their bank; funds are credited to the verified bank account configured in the reseller portal.
Each request must target an active linked merchant via merchant_id. Payers use the same StixBNK-hosted checkout as direct merchants before the bank flow.
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 reseller IBAN.
https://stixbnk.com — prepend this host to the paths below (e.g. POST https://stixbnk.com/api/v1/payments/reseller/requests). Use your own host only for local or staging environments.
Quick start
Follow these steps to run a first test payment:
- Get an active reseller API key from the portal.
- Confirm a merchant is linked,
active, and not suspended. - Call
POST /api/v1/payments/reseller/requestswithmerchant_id, amount, currency, market, reference, and a product label (product_nameorproduct.product_name). - Redirect the payer’s browser to the
checkout_urlfrom the response — StixBNK-hosted checkout (summary + merchant-of-record notice) — then the payer continues to Open Banking (Tink) or the sandbox test bank. - Configure a webhook to receive
open_banking.collection.updated.
Prerequisites
- Reseller account with a verified collection IBAN.
- At least one merchant with
activestatus (KYB approved) and a knownmerchant_id(merchants.idin the portal). - Pay Direct return URL allow-listed with the banking provider (platform admin configuration).
API authentication
Send your secret in the X-API-Key header. Requests without a key or with an inactive key receive 401 or an application error as applicable.
Content-Type: application/json
X-API-Key: your_reseller_secret_key
Create a payment request
Method POST · Path /api/v1/payments/reseller/requests
JSON body — field reference
| Field | Type | Required | Description |
|---|---|---|---|
merchant_id | integer | Yes | Linked merchant (merchants.id); must be active, not suspended, and owned by your reseller account. |
amount | number | Yes | Amount to collect (e.g. 45.00). |
currency | string | Yes | ISO 4217 code (e.g. EUR). |
market | string | Yes | Pay Direct market (e.g. FR). |
reference | string | Yes | Your business reference (order id, invoice id, etc.) — must be globally unique across Open Banking collections (shared with the merchant API). |
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. |
locale | string | No | Pay Direct link locale (e.g. en_GB, fr_FR). If omitted, language may be used to derive a locale. |
customer_email | string | No | Must be a valid email when provided. |
customer_first_name | string | No | Payer first name (stored for portal / logging). |
customer_lastname | string | No | Payer last name. Alias: customer_last_name. |
language | string | No | Short code (e.g. en, fr); used to build a Pay Direct locale when locale is not sent. |
store_name | string | No | Store or sub-brand label (stored for portal / logging; shown on 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. |
sandbox | boolean | No | If true, uses the integration sandbox (simulator bank, no Tink). Disabled environments return 403 sandbox_disabled. |
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 (the platform sets the collection IBAN according to payout settings).
Forbidden fields
Do not send payee details: recipient, iban, return_url in the body, etc.
The creditor is enforced by the platform.
{
"merchant_id": 2,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-test-014",
"product_name": "Example product",
"description": "Test Postman",
"customer_email": "kogroupcompany+28@gmail.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/reseller/requests" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_key" \
-d '{
"merchant_id": 2,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-test-014",
"product_name": "Example product",
"description": "Test Postman",
"customer_email": "kogroupcompany+28@gmail.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 (excerpt)
{
"success": true,
"payment_request_id": "33ec136e91cf4cd7bfc5919d27940f44",
"checkout_url": "https://stixbnk.com/checkout/payment/?pr=33ec136e91cf4cd7bfc5919d27940f44",
"checkout_expires_at": "2026-03-25T14:30:00Z",
"merchant_id": 42,
"merchant_business_name": "Example Merchant Ltd",
"funds_destination": "stixpay"
}
funds_destination is stixpay when the sub-merchant is set to platform payout (funds credit StixBNK’s collection account), or reseller when payout is managed by you (funds credit your verified EUR/GBP payout account for that currency).
After checkout_expires_at (UTC, ISO8601), or once the payment is final, the checkout URL returns HTTP 410.
With "sandbox": true, the success body also includes "sandbox": true and payment_request_id values start with sb_. Sub-merchant validation still applies; creditor IBAN resolution is skipped (no live Pay Direct session).
Hosted checkout (StixBNK)
The reseller API uses the same payer-facing pages as the merchant API. checkout_url always points to StixBNK so the payer sees merchant-of-record wording and the sub-merchant name (from your merchant_id) before starting the bank flow.
- Live:
{base}/checkout/payment/?pr={payment_request_id}— same two-column hosted checkout as the merchant API (summary left, Open Banking + contact/shipping from JSON right); then Tink Pay Direct. - Sandbox:
{base}/checkout/sandbox/?pr=sb_…— StixBank Sandbox shell (no MOR text), then Open bank simulator →/sandbox/ob_bank.php.
Integration sandbox (no real bank)
Same endpoint (POST /api/v1/payments/reseller/requests) with "sandbox": true. Same flow as the merchant sandbox: /checkout/sandbox/ then /sandbox/ob_bank.php — sign in with stixbank_sandbox / stixbank_sandbox, select the account that approves or declines, then redirect through the reseller Pay Direct return URL and optional success_url/failure_url.
The simulator is on by default for all environments; set OPEN_BANKING_API_SANDBOX_ENABLED to false in includes/config.php only if you need to disable it platform-wide.
funds_destination in the API response still reflects the sub-merchant’s payout setting (stixpay vs reseller) for realism; linked transactions use sandbox = 1 and do not affect ledger balances. Webhook data includes "sandbox": true.
Example request (sandbox)
Same POST URL and X-API-Key as production. Include merchant_id as usual, plus "sandbox": true. Use a unique reference per test (shared namespace with the merchant API).
{
"sandbox": true,
"merchant_id": 2,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-reseller-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/reseller/requests" \
-H "Content-Type: application/json" \
-H "X-API-Key: your_key" \
-d '{
"sandbox": true,
"merchant_id": 2,
"amount": 45.00,
"currency": "EUR",
"market": "FR",
"reference": "postman-reseller-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 is prefixed with sb_; checkout_url opens /checkout/sandbox/, then the payer uses Open bank simulator to reach /sandbox/ob_bank.php. merchant_id and merchant_business_name echo the sub-merchant you sent.
{
"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",
"merchant_id": 2,
"merchant_business_name": "Example Merchant Ltd",
"funds_destination": "stixpay",
"sandbox": true
}
Browser return after payment
After hosted checkout and the bank flow (Tink or sandbox), Pay Direct return handling runs as in production. After status sync, the browser may be redirected (302) to 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 subscribed endpoint configured in the reseller portal.
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 | reseller for payments created via the reseller 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 | Attributed merchant (merchants.id). |
callback_error / callback_error_reason | Callback error details when present. |
transaction_status | Linked transaction status when applicable. |
sandbox | true for integration simulator payments; 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 unlinked merchant —
merchant_idunknown or not under your reseller account. - Suspended merchant — operations blocked until re-enabled.
- Merchant not active — KYB not approved; status must be
active. - Missing or invalid product label —
product_name(orproduct.product_name) is required; empty, too long (>255), or a non-objectproductvalue returns400(invalid_request). - Forbidden fields — attempts to set IBAN or
return_urlin the body are rejected. - Invalid optional URLs —
success_url/failure_urlmust be valid absolute URLs. 403—sandbox_disabled— the body requested sandbox mode but the server has disabled it in configuration.409—duplicate_reference— thereferencefield must be globally unique: the same value cannot be reused for another Open Banking payment (any merchant or reseller), and it must not match an existing platform transaction reference. Choose a new reference (e.g. order id + suffix) for each payment intent.-
422—missing_bank_account— the platform cannot start checkout because no creditor bank details exist for this payment (currency + payout mode). Response includesreason(machine-readable) andmessage(human-readable). Typicalreasonvalues:
reason | When |
|---|---|
platform_eur_iban | Sub-merchant is on platform payout; EUR collection IBAN is not set (admin / env). |
platform_gbp_iban | Platform payout; GBP collection IBAN not set in admin. |
platform_collection_settings | Platform GBP settings row missing (misconfiguration). |
reseller_eur_iban | Sub-merchant is on reseller payout; no verified Europe (EUR) payout account. |
reseller_gbp_iban | Reseller payout; no verified GBP account and no usable GBP IBAN in KYB fallback. |
reseller_verified_no_iban | Verified payout row exists but IBAN is empty — update in portal. |
{
"success": false,
"error": "missing_bank_account",
"reason": "reseller_eur_iban",
"message": "No verified Europe (EUR) payout bank account. Add and verify the correct IBAN in the reseller portal (Funds → Payout bank account)."
}