Reseller portal

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.

Base URL (production). 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:

  1. Get an active reseller API key from the portal.
  2. Confirm a merchant is linked, active, and not suspended.
  3. Call POST /api/v1/payments/reseller/requests with merchant_id, amount, currency, market, reference, and a product label (product_name or product.product_name).
  4. Redirect the payer’s browser to the checkout_url from the response — StixBNK-hosted checkout (summary + merchant-of-record notice) — then the payer continues to Open Banking (Tink) or the sandbox test bank.
  5. Configure a webhook to receive open_banking.collection.updated.

Prerequisites

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.

Example headers
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

FieldTypeRequiredDescription
merchant_idintegerYesLinked merchant (merchants.id); must be active, not suspended, and owned by your reseller account.
amountnumberYesAmount to collect (e.g. 45.00).
currencystringYesISO 4217 code (e.g. EUR).
marketstringYesPay Direct market (e.g. FR).
referencestringYesYour business reference (order id, invoice id, etc.) — must be globally unique across Open Banking collections (shared with the merchant API).
product_namestringYes*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).
productobjectNoAlternative to top-level product_name: { "product_name": "Your label" }. Must be a JSON object when present; invalid shapes are rejected.
descriptionstringNoHuman-readable payment description.
localestringNoPay Direct link locale (e.g. en_GB, fr_FR). If omitted, language may be used to derive a locale.
customer_emailstringNoMust be a valid email when provided.
customer_first_namestringNoPayer first name (stored for portal / logging).
customer_lastnamestringNoPayer last name. Alias: customer_last_name.
languagestringNoShort code (e.g. en, fr); used to build a Pay Direct locale when locale is not sent.
store_namestringNoStore or sub-brand label (stored for portal / logging; shown on hosted checkout when provided).
success_urlstringNoAbsolute https URL (or http in dev). Browser redirect when the payment ends in a success terminal state. Alias: urlok.
failure_urlstringNoAbsolute URL for failure / cancel / error terminal outcomes. Alias: urlko.
sandboxbooleanNoIf 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.

Example JSON body (Postman)
{
  "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"
}
Same body via cURL
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)

JSON
{
  "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.

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).

JSON body (sandbox) — Postman
{
  "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"
}
Same sandbox request — cURL
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.

JSON (illustrative)
{
  "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.

Do not rely on this redirect alone to confirm funds: use webhooks and/or server-side verification as appropriate.

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

Verify the signature on the raw string before parsing JSON to avoid whitespace mismatches.

Event envelope
{
  "id": "evt_…",
  "type": "open_banking.collection.updated",
  "created": 1774451228,
  "data": { /* see table below */ }
}

HMAC verification (example)

PHP
$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

FieldDescription
sourceAlways open_banking for this event.
actor_typereseller for payments created via the reseller API.
payment_request_idPayment request id (matches API response).
referenceBusiness reference from creation.
amount / currencyAmount and currency.
marketMarket (may be null).
collection_statusPlatform collection status.
transfer_statusRaw bank transfer status from the provider (e.g. SENT, COMPLETED), or null.
transaction_idInternal transaction id when linked.
linked_merchant_idAttributed merchant (merchants.id).
callback_error / callback_error_reasonCallback error details when present.
transaction_statusLinked transaction status when applicable.
sandboxtrue 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

reasonWhen
platform_eur_ibanSub-merchant is on platform payout; EUR collection IBAN is not set (admin / env).
platform_gbp_ibanPlatform payout; GBP collection IBAN not set in admin.
platform_collection_settingsPlatform GBP settings row missing (misconfiguration).
reseller_eur_ibanSub-merchant is on reseller payout; no verified Europe (EUR) payout account.
reseller_gbp_ibanReseller payout; no verified GBP account and no usable GBP IBAN in KYB fallback.
reseller_verified_no_ibanVerified payout row exists but IBAN is empty — update in portal.
Example — missing_bank_account
{
  "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)."
}