BXNK Server-to-Server (S2S) API - Sandbox Testing Guide

Server-to-Server Payments

Use the S2S integration when your server owns the checkout flow and sends payment details directly to BXNK.

All Product API endpoints are served under /api/v1/product-apis. Except for token creation, requests require Authorization: Bearer <accessToken>.

Crypto S2S payments creates a vault deposit address for every request.


Base URL

Sandbox: https://sandbox-api.bxnk.com/

Your live base URL is available in the Partner Portal under API Keys — it's listed at the bottom of the page.

Authentication

Generates a short-lived Product API bearer token from an API access key and secret.

POST /api/v1/product-apis/auth/token
Content-Type: application/json

Parameters

NameTypeRequiredDescription
accessKeystringYesAPI access key. Must be a non-empty string.
secretstringYesAPI secret. Must be a non-empty string.

Request

{
  "accessKey": "test_pk_1234567890",
  "secret": "test_sk_1234567890"
}

Response 201

{
  "message": "Product API token generated successfully",
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "tokenType": "Bearer",
    "expiresAt": "2026-03-24T10:00:00.000Z"
  }
}

Errors

StatusWhen it happens
400accessKey or secret is missing or not a string.
401API key credentials are invalid.
500The token signing secret is not configured for the key's MID.

Create a Payment

Creates a payment for the authenticated MID. Omit useHostedPaymentPage or set it to false for S2S.

POST /api/v1/product-apis/payments
Authorization: Bearer <accessToken>
Content-Type: application/json
Idempotency-Key: order-1001-create

Idempotency-Key is optional for S2S payments, reused only to deduplicate retries with the same request body.

S2S supports crypto and SEPA payments. To accept card payments, use the native on-ramp hosted payment page.

Parameters

NameTypeRequiredDescription
amountnumberYesPayment amount. Must be at least 0.000005 and have no more than 8 decimal places.
currencystringYesCurrency code looked up by the backend. Must be a string with length 3..40; the service uppercases it before lookup.
paymentMethodstringConditionallyOne of card, crypto, or bank_transfer. Required when paymentMethods is omitted. Takes precedence if both fields are provided.
paymentMethodsstring[]ConditionallyNon-empty array of card, crypto, or bank_transfer. The first value is used when paymentMethod is omitted.
orderReferencestringNoMerchant order reference.
customerReferencestringNoMerchant customer reference.
successUrlURLNoCallback URL used for success events. Stored on the payment.
failureUrlURLNoCallback URL used for failed, cancelled, or expired events. Stored on the payment.
expiryTimeISO 8601 stringNoFuture expiry timestamp. Invalid or past values return 400.
useHostedPaymentPagebooleanNoOmit or set to false for S2S.
metadataobjectConditionallyMethod-specific keys are read from this object: asset/network for crypto, and sandbox scenario keys. Required whenever the payment method needs those keys.
metadata.m_idstringNoMID to create the payment for. The MID embedded in the Product API bearer token is used automatically, so this key can be omitted when calling with a Product API token; it is only consulted as a fallback when the token carries no MID.

Crypto Request

Crypto payments require both metadata.asset and metadata.network. The PSP must have a settlement wallet configured for the requested asset, and the requested amount must convert to at least the configured USD minimum for that currency.

{
  "amount": 500,
  "currency": "USDT",
  "paymentMethod": "crypto",
  "metadata": {
    "m_id": "MID-ABCDEFGHIJ",
    "asset": "USDT",
    "network": "ethereum"
  }
}

Crypto Response 201

{
  "message": "Payment created successfully",
  "data": {
    "paymentId": "PAY-x9y8z7w6v5u4",
    "status": "created",
    "amount": 500,
    "currency": "USDT",
    "paymentMethod": "crypto",
    "paymentFlow": "S2S",
    "orderReference": null,
    "customerReference": null,
    "hostedPaymentUrl": null,
    "expiresAt": null,
    "instructions": null,
    "transactionId": "tx_abc123def456",
    "mId": "MID-ABCDEFGHIJ",
    "walletAddress": "0x11d94ac1d603c28c03de643e5e67e66408f3ee89",
    "qrCodeUrl": "data:image/png;base64,iVBORw0KGgo...",
    "customerName": null,
    "customerEmail": null,
    "customerWallet": null,
    "createdAt": "2026-03-20T10:05:00.000Z",
    "successUrl": null,
    "failureUrl": null,
    "redirectUrl": null,
    "declineReason": null
  }
}

walletAddress and qrCodeUrl are populated immediately — BXNK allocates a real vault deposit address on every crypto payment request, in both TEST and LIVE mode. There is no synthetic crypto sandbox outcome for S2S: the payment only progresses once BXNK's indexer observes an on-chain transfer to walletAddress. instructions remains null until that transfer is indexed, at which point it's populated with the indexed transfer event shown under Verify a Payment.

Create Payment Errors

StatusWhen it happens
400Validation fails, no MID can be resolved from the token or metadata.m_id, paymentMethod and paymentMethods are both omitted, currency is unknown, expiryTime is invalid or not in the future, or method-specific requirements are not met.
400Crypto request omits metadata.network or metadata.asset, uses an unsupported asset/network, the deposit amount converts to less than the configured USD minimum, or the PSP has no settlement wallet configured for the asset.
400SEPA S2S request is submitted for a LIVE-mode API key.
401Bearer token is missing, expired, or invalid.
403Authenticated token is not authorized by the guard.
503Crypto payment creation has been disabled for the environment. This gate applies only to crypto; there is no equivalent disable switch for card or SEPA creation.

Retrieve a Payment

Returns payment details by ID.

GET /api/v1/product-apis/payments/{paymentId}
Authorization: Bearer <accessToken>

Response 200

Returns the same payment object shape as create payment. The payment status is one of created, awaiting_otp, processing, completed, failed, expired, refunded, or cancelled.

Payment retrieval and status-check endpoints are each limited to 5 requests per second. For provider reconciliation, use GET /api/v1/product-apis/payments/{paymentId}/status-check for unfinished, failed, or expired payments when applicable; it may update the stored status.

Errors

StatusWhen it happens
401Bearer token is missing, expired, or invalid.
403Authenticated token is not authorized by the guard.
404Payment is not found.

List Payments

Returns payments for the MID embedded in the authenticated token.

GET /api/v1/product-apis/payments?page=1&limit=10&status=completed&paymentMethod=card&currency=EUR&sortBy=createdAt&sortOrder=DESC
Authorization: Bearer <accessToken>

Query Parameters

NameTypeRequiredDescription
pagenumberNoDefaults to 1. Minimum 1.
limitnumberNoDefaults to 10. Minimum 10, maximum 100.
sortBystringNoOne of createdAt, amount, or status. Defaults to createdAt.
sortOrderstringNoASC or DESC. Defaults to DESC.
statusstringNoOne of created, awaiting_otp, processing, completed, failed, expired, refunded, cancelled.
paymentMethodstringNoOne of card, crypto, or bank_transfer.
currencystringNoCurrency code. If no matching currency exists, the endpoint returns an empty page.
orderReferencestringNoExact match on the merchant order reference.
customerReferencestringNoExact match on the merchant customer reference.
fromISO 8601 stringNoFilters payments created on or after this timestamp.
toISO 8601 stringNoFilters payments created on or before this timestamp.
searchstringNoAccepted for validation but currently has no effect on the returned results — this endpoint does not apply it as a filter.

Refund a Payment

Refunds a completed payment. Both partial and full refunds are supported.

Sandbox limitation: Partial refunds are not supported in the sandbox environment. In sandbox, refund the full payment amount only. Partial refunds will work in production.

POST /api/v1/product-apis/payments/{paymentId}/refund
Authorization: Bearer <accessToken>
Content-Type: application/json
Idempotency-Key: order-1001-refund

Parameters

NameTypeRequiredDescription
paymentIdstringYesPayment ID to refund.
refundAmountnumberYesAmount to refund. Must be at least 0.01, have no more than 8 decimal places, and less or equal to the original payment amount.
reasonstringNoRefund reason.

Request

{
  "refundAmount": 49.99,
  "reason": "Customer requested refund"
}

Response 201

{
  "message": "Refund processed successfully",
  "data": {
    "refundId": "REF-XYZ123ABC",
    "paymentId": "PAY-a1b2c3d4e5f6",
    "refundAmount": 49.99,
    "refundStatus": "completed",
    "reason": "Customer requested refund",
    "createdAt": "2026-03-20T11:00:00.000Z",
    "refundedAmount": 49.99,
    "remainingRefundableAmount": 0,
    "isFullyRefunded": true
  }
}

refundStatus is one of processing, completed, or failed. The cumulative fields show the total completed refund amount, the amount still refundable, and whether the payment is fully refunded.

Use GET /api/v1/product-apis/payments/{paymentId}/refund/{refundId} to retrieve the current status of a refund. This endpoint is limited to 5 requests per second.

Errors

StatusWhen it happens
400Payment is not completed, the payment has already been fully refunded, the requested amount exceeds the remaining refundable amount, payment currency is missing, or PSP balance would become negative.
403Payment MID does not match the authenticated token.
404Payment is not found.
429Polling rate limit exceeded. Payment retrieval, status-check, and refund-status endpoints allow up to 5 requests per second.

Webhook Endpoints

Register webhook endpoints before creating payments.

Strongly recommended: register a webhook endpoint for every new MID as soon as it's created to receive real-time payment status updates.

Create Webhook Endpoint

POST /api/v1/product-apis/psp/webhooks
Authorization: Bearer <accessToken>
Content-Type: application/json
NameTypeRequiredDescription
urlURLYesPublic URL that receives webhook POSTs.
mIdstringYesMID for this endpoint. Must belong to the authenticated PSP.
secretstringNoSigning secret. Must be length 6..255. If omitted, BXNK generates one.
isActivebooleanNoDefaults to true.

The create response includes secret. List and status update responses do not include it.

List Webhook Endpoints

GET /api/v1/product-apis/psp/webhooks
Authorization: Bearer <accessToken>

Returns active and inactive endpoints for MIDs owned by the authenticated PSP, excluding soft-deleted endpoints.

Update Webhook Status

PATCH /api/v1/product-apis/psp/webhooks/{endpointId}/status
Authorization: Bearer <accessToken>
Content-Type: application/json
{
  "isActive": false
}

Rotate Webhook Secret

POST /api/v1/product-apis/psp/webhooks/{endpointId}/rotate-secret
Authorization: Bearer <accessToken>

Returns id, mId, the new secret, and updatedAt.

Webhook Delivery

Webhook requests are JSON POST requests. BXNK signs the exact JSON string with HMAC-SHA256 and sends the signature in x-psp-signature when a signing secret exists.

Registered active endpoints for the payment MID are used first. If none exist and DEFAULT_WEBHOOK_URL is configured, BXNK delivers to that fallback URL and signs with the PSP signing secret.

const express = require('express');
const crypto = require('crypto');

const app = express();

app.post('/bxnk/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-psp-signature'];
  if (!signature) {
    return res.status(401).send('Missing signature');
  }

  const expectedSignature = crypto
    .createHmac('sha256', process.env.BXNK_WEBHOOK_SECRET)
    .update(req.body) // pass the Buffer directly, no need to .toString() first
    .digest('hex');

  const expectedBuf = Buffer.from(expectedSignature, 'hex');
  const receivedBuf = Buffer.from(signature, 'hex');

  if (
    expectedBuf.length !== receivedBuf.length ||
    !crypto.timingSafeEqual(expectedBuf, receivedBuf)
  ) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString('utf8'));
  return res.sendStatus(200);
});

Payment lifecycle events are emitted as payment.created, payment.awaiting_otp, payment.processing, payment.completed, payment.failed, payment.expired, payment.refunded, and payment.cancelled.


Sandbox Scenarios

SEPA S2S sandbox outcomes are driven by the debtor IBAN you submit. Use one of the following sandbox debtor IBANs:

Debtor IBANFinal statusDecline reason
DE89370400440532013000completed—
DE91370400440532013000failedinsufficient_funds
DE12500105170648489890failediban_invalid

Crypto has no synthetic sandbox outcome for S2S: outcomes always depend on the real deposit address and the on-chain transfer indexed against it (see the Crypto Request section above).


Product API Quick Reference

MethodEndpointDescription
POST/api/v1/product-apis/auth/tokenGenerate a Product API bearer token.
POST/api/v1/product-apis/paymentsCreate an HPP or S2S payment.
GET/api/v1/product-apis/paymentsList payments for the authenticated MID.
GET/api/v1/product-apis/payments/{paymentId}Retrieve a payment.
GET/api/v1/product-apis/payments/{paymentId}/status-checkCheck status and resume a non-terminal payment if possible.
POST/api/v1/product-apis/payments/{paymentId}/refundRefund a completed payment.
GET/api/v1/product-apis/payments/{paymentId}/refund/{refundId}Retrieve refund status.
POST/api/v1/product-apis/psp/webhooksCreate a webhook endpoint.
GET/api/v1/product-apis/psp/webhooksList webhook endpoints.
PATCH/api/v1/product-apis/psp/webhooks/{endpointId}/statusEnable or disable a webhook endpoint.
POST/api/v1/product-apis/psp/webhooks/{endpointId}/rotate-secretRotate a webhook signing secret.
GET/api/v1/product-apis/psp/balanceGet PSP balance.
GET/api/v1/product-apis/psp/balances/assetsGet PSP asset balances.
GET/api/v1/product-apis/psp/chargeback-configGet PSP chargeback config.
GET/api/v1/product-apis/payment-methods/activeList active payment methods.
GET/api/v1/product-apis/payment-methods/pspList the authenticated PSP's payment methods.
GET/api/v1/product-apis/payment-methods/psp/{paymentMethodId}/currenciesList currencies assigned to a PSP payment method.
GET/api/v1/product-apis/currenciesList currencies.
GET/api/v1/product-apis/currencies/pspList currencies supported by the authenticated PSP.
POST/api/v1/product-apis/storage/presigned-uploadGenerate a presigned S3 upload URL.
GET/api/v1/product-apis/storage/presigned-download/{key}Generate a presigned S3 download URL.
DELETE/api/v1/product-apis/storage/delete/{key}Delete an S3 object.
GET/api/v1/product-apis/healthzCheck database health.