Which APIs are affected by this migration?
- Legacy: Payments API v1.3.2 (multi-country)
- New:
- Payments SE Private API v2.0.0
- Payments SE Corporate API v2.0.0
The Accounts API (v2.1.30) and Subscriptions API (v1.0.22) are not part of this migration.
Payments SE Corporate API v2.0.0Where do I start?
- Review the new OpenAPI specifications on the Developer Portal.
- Subscribe your app to the relevant product (
payments-se-privateand/orpayments-se-corporate) via the Subscriptions API. - Develop and test in the Sandbox environment.
- Use the Go Live process on the Developer Portal when ready for production.
What's the recommended migration path?
- Subscribe your existing app to the new products via the Subscriptions API:
payments-se-private(PIS scope)payments-se-corporate(PIS scope)
- Update base URL from
/openbanking/psd2/v1to/openbanking/psd2/se/v2. - Split your code paths by customer segment (private vs corporate).
- Map your product calls to the new endpoint structure (
/single/{product}vs/signing-basket). - Adapt payloads to the product-specific schemas — particularly
creditorAccount.accountType,chargeBearer,paymentTypeInformationand remittance text patterns. - Move SEK Credit Transfer flows into baskets — single
sek-credit-transferis not supported in v2. - Re-test SCA flows — both
DECOUPLEDandREDIRECTare still supported, but URLs and link structures are versioned. - Regression-test cancellation flows for future-dated payments.
- Verify in Sandbox, then complete Go Live on the Developer Portal.
Which payment products are supported in the new Swedish APIs?
| Product | Code (URL identifier) | Private | Corporate |
|---|---|---|---|
| Handelsbanken Domestic Credit Transfer (SHBCT) | handelsbanken-domestic-credit-transfer | ✓ | ✓ |
| SEK Credit Transfer (SEKCT) — former bank transfers and giro payments | sek-credit-transfer | ✓ (basket only) | ✓ (basket only) |
| SEPA Credit Transfer (SEPACT) | sepa-credit-transfer | – | ✓ |
| Cross Currency Credit Transfer (CCCT) | cross-currency-credit-transfer | ✓ | ✓ |
Handelsbanken Domestic Credit Transfer is for Handelsbanken accounts only
The payment product handelsbanken-domestic-credit-transfer (SHBCT) can only be used to credit accounts held at Handelsbanken. It cannot be used to pay an account at another Swedish bank.
This restriction is enforced by the schema. A request that targets an account at another bank will be rejected with an HTTP 400, typically referencing creditorAgent.identification.code and creditorAccount.value.
If the destination clearing number falls outside 6000–6999, the account is not a Handelsbanken account and this product is not the correct one to use.
Paying an account at another Swedish bank
To send a domestic SEK payment to an account held at another Swedish bank, use the SEK Credit Transfer product (sek-credit-transfer, SEKCT) instead. SEKCT accepts accountType values IBAN, BBAN, PG and BG, so you can pass a Swedish IBAN directly without splitting out the clearing number.
Note that SEKCT is available only through a signing basket. There is no single-payment endpoint for this product.
What are the new base URLs?
| Environment | URL |
|---|---|
| Sandbox | https://sandbox.handelsbanken.com/openbanking/psd2/se/v2 |
| Live | https://api.handelsbanken.com/openbanking/psd2/se/v2 |
Note the new /se/v2 path segment. The legacy API used /openbanking/psd2/v1.
What is the URL structure for the new endpoints?
The new APIs split clearly between customer segment and payment type:
/payments/private/single/{paymentProduct}— single private payments/payments/private/signing-basket— private basket payments/payments/corporate/single/{paymentProduct}— single corporate payments/payments/corporate/signing-basket— corporate basket payments/payments/private/accounts/shorttermconsents— short-term consent (Private only)
In v1.3.2, all payments used a single /payments/{paymentProduct} endpoint regardless of customer type.
What is the mapping from legacy product names to the new ones?
| Legacy v1.3.2 product | New SE v2.0.0 product |
|---|---|
swedish-domestic-giro-payment | sek-credit-transfer (SEKCT, basket only) |
swedish-domestic-credit-transfer | sek-credit-transfer (SEKCT, basket only) |
sepa-credit-transfer | sepa-credit-transfer (Corporate only) |
cross-currency-credit-transfer | cross-currency-credit-transfer |
| (new in v2) | handelsbanken-domestic-credit-transfer (SHBCT) |
Important: In v2, SEKCT can only be executed inside a signing basket — single SEKCT payments are no longer supported. SHBCT, SEPACT and CCCT can only be executed as single payments.
How have signing baskets changed?
| Aspect | Legacy v1.3.2 | New v2.0.0 |
|---|---|---|
| Eligibility | Sweden, individuals, swedish-domestic-giro-payment only | Both Private and Corporate, sek-credit-transfer |
| Max payments per basket | 30 | 30 (Private) / 100 (Corporate) |
| Min payments per basket | 1 | 1 |
What are the main structural changes in the payment payload?
- Dedicated product-specific schemas: each product (SHBCT, SEKCT, SEPACT, CCCT) now has its own validated request schema with the right field set, account types and patterns. The legacy API used one generic
PaymentPayloadfor all products. creditorAccount.accountTypeis now strictly enumerated per product:- SHBCT —
BBANonly (8–9 digit Swedish SHB account number) - SEKCT —
BBAN,IBAN(Swedish IBANs only),PG,BG - SEPACT, CCCT Nordic, CCCT EU-payment —
IBANonly - CCCT Normal / Express —
IBANorBBAN
- SHBCT —
chargeBearervalues are now tied to product:SHAR— SEPACT, CCCT Nordic, CCCT EU-paymentCHAR,CRED,DEBT— CCCT Normal and Express
PaymentTypeInformationuses ISO 20022 codes (SEPA,URGP,NURG) and category-purpose codes (SUPP,NOPA,EUSE).- Stricter character patterns apply to
creditor.name,remittanceInformation.textand similar fields — particularly for SHBCT, where remittance text is limited to 14 characters.
Can I use the V2 to Cancel or ask for Status on payments created in V1?
No, You must use the old endpoint (V1) to cancel and ask for status on payments.
Is SEK Credit Transfer available only as a basket payment?
Yes. There is no /payments/corporate/single/sek-credit-transfer endpoint. SEK credit transfers can be initiated only via POST /payments/corporate/signing-basket.
A basket containing a single payment is the correct and supported approach: the request requires the sekCreditTransfers array with minItems 1 (maxItems 100), so an array of length 1 is the intended way to initiate an individual cross-bank SEK transfer.
Why must I use the signing basket reference throughout the whole flow — and not the payment ID?
When you initiate a basket payment (for example SEKCT, which is basket-only), the response gives you two different kinds of identifier. Using the wrong one in the signing and execution steps is the single most common cause of authorization failures we see during migration. This article explains which ID to use, where, and why.
The two identifiers you receive
A successful call to POST /payments/private/signing-basket (or /payments/corporate/signing-basket) returns a 201 containing:
- One
signingBasketId— the identifier for the basket as a whole. - A
paymentsarray, where each individual payment in the basket has its ownpaymentIdandtppPaymentReference.
In other words, the response looks like this in shape: { "signingBasketId": "...", "payments": [ { "paymentId": "...", "tppPaymentReference": "..." } ] }
Both IDs are valid and both have a purpose — but they are not interchangeable.
The rule
For a basket payment, the SCA authorisation, the token exchange, and the execution call must all reference the
signingBasketId. ThepaymentIdvalues identify the individual payments inside the basket; they are never used to drive signing or execution of the basket.
Where each step uses the basket ID
- Initiate the basket. Call
POST /payments/private/signing-basket. Read thesigningBasketIdfrom the response and keep it. - Start SCA. Build the authorisation request that redirects the PSU so that it is bound to the
signingBasketId. The consent the PSU gives is for the basket, not for one payment within it. - Exchange the code for a token. The access token you obtain after the redirect is bound to that same
signingBasketId. Do not start a new authorisation against apaymentIdat this point. - Execute the basket. Call
PUT /payments/private/signing-basket/{signingBasketId}, using the samesigningBasketIdas the path parameter and the token from step 3.
The same four steps apply to the Corporate API using the /payments/corporate/signing-basket paths.
The pitfall: what happens if you use a paymentId instead
If you pick one of the paymentId values out of the payments array and use it to drive SCA or the token exchange, the identifier you authorise will not match the resource you are trying to execute. The typical symptoms are:
- A
401or403at the authorisation or token step, because the token is not valid for the resource being addressed. - A
PUTto the basket that fails because the token was bound to a payment, not to the basket. - Intermittent success in test and failure in production, when the IDs happen to be cross-wired only some of the time.
These errors are raised before any payment processing takes place, so retrying the payment payload will not help. The fix is always to align the identifier: use the signingBasketId consistently from the authorisation redirect through to the PUT execution.
Quick checklist before you go live
- Am I storing the
signingBasketIdfrom the initiation response, and not apaymentId? - Is my authorisation redirect bound to the
signingBasketId? - Is the access token I use for execution the one obtained for that
signingBasketId? - Does my
PUTpath read/signing-basket/{signingBasketId}with the same value?
If all four answer yes, the identifier chain is consistent and the basket will sign and execute correctly.
How do we check status after executing a basket?
There is no basket-level status endpoint. Use the existing per-payment endpoint GET /payments/corporate/{paymentProduct}/{paymentId}/status with paymentProduct = sek-credit-transfer and the paymentId returned per payment in the initiation response.
The initiation response returns a payments array, each element carrying its own paymentId plus your tppPaymentReference; use that mapping to poll each payment.
On PARTIAL_FAILURE, how do we identify which payments failed?
The basket execute response returns only a basket-leveltransactionStatus (SUCCEEDED / FAILED / PARTIAL_FAILURE); it does not break results down per payment.
To identify individual outcomes, call the per-payment status endpoint for each paymentId. At payment level, transactionStatus uses ISO 20022 codes (ACTC, ACCP, ACSC, ACCC, PATC, CANC, RJCT). A failed payment shows RJCT, with reasonCode and reason giving the specific cause.
Recommended PARTIAL_FAILURE pattern
- Execute basket. If
transactionStatus = PARTIAL_FAILURE,- Iterate the
paymentsarray from the initiation response,- Call
GET .../sek-credit-transfer/{paymentId}/statusfor each, treatingRJCT+reasonCode/reasonas the failure detail.
What are the cut-off times and retry logic for SEK credit transfers?
A SEK credit transfer is a payment to an account held with Handelsbanken (SHB) or another bank on the Swedish market. Approved payments are processed through Swedish (SE) clearing on the same day.
Cut-off times
Cut-off times apply on business days and are stated in CET. From 4 May, the cut-off times are 03:45, 07:45, 08:45, 09:45, 10:45, 11:45 and 12:45. From 15 May, the cut-off times are extended to also include 13:45 and 14:45.
New attempts (retry logic)
If a payment cannot be settled at the first attempt, further attempts are made automatically. The rules differ between private and corporate payers.
- Individuals: New attempts are made during the payment date, prior to the cut-off times, and on the following two banking days. If the payment is still unsuccessful, it is permanently stopped due to lack of funds.
- Corporate: New attempts are made during the payment date, prior to the cut-off times. If the payment is still unsuccessful, it is permanently stopped after the last clearing cycle.
Where do I get support?
- Developer Portal:https://developer.handelsbanken.com/api/
- Technical guidelines:https://developer.handelsbanken.com/api/psd2/guidelines/payments
- Sandbox info:https://developer.handelsbanken.com/api/sandbox/geninfo
- Country-specific (SE):https://developer.handelsbanken.com/api/apis/payments/se
- Go Live:https://developer.handelsbanken.com/api/psd2/live