Developer documentation

BjornTech Swish Platform API

Customer integration guide for merchant onboarding, signed requests, payments, refunds and reliable status handling.

Productionhttps://swish.bjorntech.eu

Overview

Conventions and security

Append every documented route directly to the production base URL. Requests and responses use JSON unless otherwise noted.

HTTPS only

Use HTTPS for every production request.

Server-only credentials

Never expose access keys or HMAC secrets in browser or mobile code.

Authoritative status

Treat the authenticated GET endpoint as the source of truth.

Stable identifiers

Generate a unique instructionId for each new payment or refund.

01

Merchant onboarding

Before onboarding, the merchant needs a Swish Handel number from their bank with BjornTech AB (id 9872884581) as technical provider. See Get started for bank-by-bank instructions.

Onboarding links a merchant account to the integration and returns the credentials required for signed API requests.

POST/v1/onboarding/startStart merchant onboarding
GET/v1/onboarding/confirm?token=...Complete confirmation and redirect
POST/v1/onboarding/exchangeExchange one-time code for credentials

Start onboarding

POST/v1/onboarding/start
FieldRequiredDescription
emailYesMerchant email address
merchantAliasYesMerchant Swish alias
redirectUri or adminReturnUrlYes (one)Browser return URL after confirmation
stateNoOpaque state protection value
nonceNoOpaque unique integration value
callbackUrlNoPayment-status callback endpoint
siteUrlNoMerchant-site metadata
integrationVersionNoIntegrating application version
Production return URLs must be explicitly configured, absolute HTTPS, externally reachable, and match an approved origin and implemented path. Never derive them from request or forwarding headers, and never fall back to localhost.

Confirm and exchange

GET /v1/onboarding/confirm?token=... returns the browser to the stored URL with swish_onboarding_code and state. Validate state server-side, exchange the transient code immediately, and clean it from the URL.

exchange request
POST /v1/onboarding/exchange
Content-Type: application/json

{ "code": "ex-opaque" }

Store the returned base64 HMAC secret and access key securely on the server. Codes are single-use and expire after 15 minutes by default. Unknown, expired and used codes return 404, 410 and 409 respectively.

02

HMAC authentication

Sign every payment, refund, status and cancellation request with HMAC-SHA256. Build exactly seven fields, separated by LF newlines, with no trailing newline.

canonical request
METHOD
PATH
QUERY
ACCESS_KEY_ID
TIMESTAMP
NONCE
BODY_SHA256_HEX

Use the uppercase method, exact transmitted pathname and encoded query substring, and the lowercase SHA-256 digest of the exact UTF-8 body. Hash the empty string for a bodyless request.

Required headerValue
X-BjornTech-Access-KeyAssigned accessKeyId
X-BjornTech-TimestampISO-8601 timestamp with timezone
X-BjornTech-NonceUnique, unpredictable value
X-BjornTech-SignatureLowercase hex HMAC-SHA256 signature
Base64-decode the HMAC secret once and use the raw bytes as the key. Timestamps must normally be within five minutes. Every retry needs a fresh timestamp, nonce and signature. Never combine these headers with Authorization: Bearer.

03

Payments

PUT/v1/swish/payment-requests/:instructionIdCreate payment
GET/v1/swish/payment-requests/:instructionIdAuthoritative status
PATCH/v1/swish/payment-requests/:instructionIdCancel payment

Create a payment

PUT/v1/swish/payment-requests/:instructionId
FieldRequiredDescription
payeeAliasYesMerchant Swish number
amountYesDecimal string, max two decimals
currencyYesMust be SEK
callbackUrlNoHTTPS status callback; omit for polling only
payerAliasNoPayer mobile number, international format
payeePaymentReferenceNoMerchant payment reference
messageNoMessage shown for the payment
payment request
{
  "payeePaymentReference": "ORDER-10001",
  "callbackUrl": "https://merchant.example/swish/callback",
  "payerAlias": "46712345678",
  "payeeAlias": "1233801768",
  "amount": "100.00",
  "currency": "SEK",
  "message": "Order 10001"
}
A successful creation means the payment request was created—not that payment succeeded. Verify using authenticated GET.

Payment statuses

StatusMeaning
CREATEDCreated, not complete
PAIDPayment successful
DECLINEDPayer declined
ERRORFailed or timed out
CANCELLEDPayment request cancelled

Cancel a payment

PATCH/v1/swish/payment-requests/:instructionId

Send Content-Type: application/json-patch+json and the exact patch below. A payment can only be cancelled before reaching a final state.

cancellation body
[
  { "op": "replace", "path": "/status", "value": "cancelled" }
]

04

Refunds

PUT/v1/swish/refunds/:instructionIdCreate refund
GET/v1/swish/refunds/:instructionIdAuthoritative refund status

Create a refund

FieldRequiredDescription
originalPaymentReferenceYesReference from the original successful payment
payerAliasYesMerchant Swish number issuing the refund
amountYesDecimal string; cannot exceed refundable amount
currencyYesMust be SEK
callbackUrlYesHTTPS refund-status endpoint
payerPaymentReferenceNoMerchant refund reference
messageNoRefund message
refund request
{
  "originalPaymentReference": "652ED6A2BCDE4BA8AD11D7334E9567B7",
  "callbackUrl": "https://merchant.example/swish/refund-callback",
  "payerAlias": "1233801768",
  "amount": "100.00",
  "currency": "SEK",
  "payerPaymentReference": "REFUND-10001",
  "message": "Refund for order 10001"
}

A successful response means Swish accepted the refund for processing. The body may be empty. Retrieve the refund for authoritative status.

Refund statuses

StatusMeaning
CREATEDCreated, not complete
DEBITEDAmount withdrawn from merchant
PAIDRefund successful
ERRORRefund failed

05

Callbacks and status handling

Callbacks are enabled only when an individual request includes callbackUrl. Polling-only is supported by omitting it.

Treat callbacks as notifications, never proof of payment.
Correlate identifiers, then use authenticated GET to verify status.
Make callback processing idempotent and tolerant of repeated delivery.
Do not assume callback headers reproduce optional creation fields.

06

Rate limits and retries

~1,000 requests

per source IP over five minutes — approximately 3.3 requests/second average.

On 429 Too Many Requests, read Retry-After, pause the affected source IP for at least that duration, then continue with bounded backoff and jitter. Do not assume the response body is JSON.

For an uncertain payment or refund create outcome, reuse the same instruction ID and exact payload—but generate a fresh timestamp, nonce and signature.

07

Errors

Application errors are generally JSON, but upstream and rate-limit responses may be JSON, non-JSON or empty. Use HTTP status as the primary signal and parse bodies defensively.

Implementation rule

Never infer undocumented endpoints, SDKs, statuses, browser handoff URLs, authentication methods, response shapes or guarantees.