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/jsonParameters
| Name | Type | Required | Description |
|---|---|---|---|
accessKey | string | Yes | API access key. Must be a non-empty string. |
secret | string | Yes | API secret. Must be a non-empty string. |
Request
{
"accessKey": "test_pk_1234567890",
"secret": "test_sk_1234567890"
}Response 201
201{
"message": "Product API token generated successfully",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"tokenType": "Bearer",
"expiresAt": "2026-03-24T10:00:00.000Z"
}
}Errors
| Status | When it happens |
|---|---|
400 | accessKey or secret is missing or not a string. |
401 | API key credentials are invalid. |
500 | The 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-createIdempotency-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
| Name | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Payment amount. Must be at least 0.000005 and have no more than 8 decimal places. |
currency | string | Yes | Currency code looked up by the backend. Must be a string with length 3..40; the service uppercases it before lookup. |
paymentMethod | string | Conditionally | One of card, crypto, or bank_transfer. Required when paymentMethods is omitted. Takes precedence if both fields are provided. |
paymentMethods | string[] | Conditionally | Non-empty array of card, crypto, or bank_transfer. The first value is used when paymentMethod is omitted. |
orderReference | string | No | Merchant order reference. |
customerReference | string | No | Merchant customer reference. |
successUrl | URL | No | Callback URL used for success events. Stored on the payment. |
failureUrl | URL | No | Callback URL used for failed, cancelled, or expired events. Stored on the payment. |
expiryTime | ISO 8601 string | No | Future expiry timestamp. Invalid or past values return 400. |
useHostedPaymentPage | boolean | No | Omit or set to false for S2S. |
metadata | object | Conditionally | Method-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_id | string | No | MID 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
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
| Status | When it happens |
|---|---|
400 | Validation 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. |
400 | Crypto 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. |
400 | SEPA S2S request is submitted for a LIVE-mode API key. |
401 | Bearer token is missing, expired, or invalid. |
403 | Authenticated token is not authorized by the guard. |
503 | Crypto 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
200Returns 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
| Status | When it happens |
|---|---|
401 | Bearer token is missing, expired, or invalid. |
403 | Authenticated token is not authorized by the guard. |
404 | Payment 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¤cy=EUR&sortBy=createdAt&sortOrder=DESC
Authorization: Bearer <accessToken>Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | number | No | Defaults to 1. Minimum 1. |
limit | number | No | Defaults to 10. Minimum 10, maximum 100. |
sortBy | string | No | One of createdAt, amount, or status. Defaults to createdAt. |
sortOrder | string | No | ASC or DESC. Defaults to DESC. |
status | string | No | One of created, awaiting_otp, processing, completed, failed, expired, refunded, cancelled. |
paymentMethod | string | No | One of card, crypto, or bank_transfer. |
currency | string | No | Currency code. If no matching currency exists, the endpoint returns an empty page. |
orderReference | string | No | Exact match on the merchant order reference. |
customerReference | string | No | Exact match on the merchant customer reference. |
from | ISO 8601 string | No | Filters payments created on or after this timestamp. |
to | ISO 8601 string | No | Filters payments created on or before this timestamp. |
search | string | No | Accepted 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-refundParameters
| Name | Type | Required | Description |
|---|---|---|---|
paymentId | string | Yes | Payment ID to refund. |
refundAmount | number | Yes | Amount to refund. Must be at least 0.01, have no more than 8 decimal places, and less or equal to the original payment amount. |
reason | string | No | Refund reason. |
Request
{
"refundAmount": 49.99,
"reason": "Customer requested refund"
}Response 201
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
| Status | When it happens |
|---|---|
400 | Payment 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. |
403 | Payment MID does not match the authenticated token. |
404 | Payment is not found. |
429 | Polling 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| Name | Type | Required | Description |
|---|---|---|---|
url | URL | Yes | Public URL that receives webhook POSTs. |
mId | string | Yes | MID for this endpoint. Must belong to the authenticated PSP. |
secret | string | No | Signing secret. Must be length 6..255. If omitted, BXNK generates one. |
isActive | boolean | No | Defaults 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 IBAN | Final status | Decline reason |
|---|---|---|
DE89370400440532013000 | completed | — |
DE91370400440532013000 | failed | insufficient_funds |
DE12500105170648489890 | failed | iban_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
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/product-apis/auth/token | Generate a Product API bearer token. |
POST | /api/v1/product-apis/payments | Create an HPP or S2S payment. |
GET | /api/v1/product-apis/payments | List payments for the authenticated MID. |
GET | /api/v1/product-apis/payments/{paymentId} | Retrieve a payment. |
GET | /api/v1/product-apis/payments/{paymentId}/status-check | Check status and resume a non-terminal payment if possible. |
POST | /api/v1/product-apis/payments/{paymentId}/refund | Refund a completed payment. |
GET | /api/v1/product-apis/payments/{paymentId}/refund/{refundId} | Retrieve refund status. |
POST | /api/v1/product-apis/psp/webhooks | Create a webhook endpoint. |
GET | /api/v1/product-apis/psp/webhooks | List webhook endpoints. |
PATCH | /api/v1/product-apis/psp/webhooks/{endpointId}/status | Enable or disable a webhook endpoint. |
POST | /api/v1/product-apis/psp/webhooks/{endpointId}/rotate-secret | Rotate a webhook signing secret. |
GET | /api/v1/product-apis/psp/balance | Get PSP balance. |
GET | /api/v1/product-apis/psp/balances/assets | Get PSP asset balances. |
GET | /api/v1/product-apis/psp/chargeback-config | Get PSP chargeback config. |
GET | /api/v1/product-apis/payment-methods/active | List active payment methods. |
GET | /api/v1/product-apis/payment-methods/psp | List the authenticated PSP's payment methods. |
GET | /api/v1/product-apis/payment-methods/psp/{paymentMethodId}/currencies | List currencies assigned to a PSP payment method. |
GET | /api/v1/product-apis/currencies | List currencies. |
GET | /api/v1/product-apis/currencies/psp | List currencies supported by the authenticated PSP. |
POST | /api/v1/product-apis/storage/presigned-upload | Generate 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/healthz | Check database health. |