Information on Handelsbanken Payment API version 2

Update regarding V2 Payments API for Swedish customers

Handelsbanken Relaunches Payment API V2

The PIS API version 2 has now been relaunched. This version is identical to the previously released version, with the addition of a new endpoint.

Our Payment API V2 now supports the SEK Credit Transfer endpoint for the new local domestic payment type in Sweden.

In addition, we have introduced a new endpoint for both corporate and private customers: other-bank-domestic-credit-transfer. This endpoint should be used for payments that need to be debited immediately after successfully passing all internal validation checks. We understand that this functionality is important for some Third-Party Providers (TPPs). For more information, please refer to the FAQ for ‘Payments to other Banks’.

Please note that, due to changes to our account offerings, it will no longer be possible to initiate payments to other banks from the account type Allkortskonto.

Important Timeline

We kindly ask you to review the documentation available on our Developer Portal and test your integration in our Sandbox environment.

Please be aware that we will shortly be deprecating PIS API V1 and is scheduled to be decommissioned on the 15th November 2026.

Previously communicated information about SEK Credit transfer (from March 2026)

FAQ SEK Credit Transfer

SEK Credit Transfer FAQs

A note on paths in this FAQ - Start here

The Payments SE Private API and the Payments SE Corporate API share the same path structure. Where a path below is written with {segment}, substitute private or corporate according to the customer segment you are serving — for example GET /payments/private/sek-credit-transfer/{paymentId}/status or GET /payments/{segment}/sek-credit-transfer/{paymentId}/status.

Endpoints that exist in only one of the two APIs are marked explicitly. Short-term consent and the payment accounts endpoint are available in the Private API only.

Which APIs are affected by this migration?

  • Legacy: Payments API v1.3.2 (multi-country)
  • New:
    • Payments SE Private API v2.0.3
    • Payments SE Corporate API v2.0.3

The Accounts API (v2.1.30) and Subscriptions API (v1.0.22) are not part of this migration.

Where do I start?

  1. Review the new OpenAPI specifications on the Developer Portal.
  2. Subscribe your app to the relevant product (payments-se-private and/or payments-se-corporate) via the Subscriptions API.
  3. Develop and test in the Sandbox environment.
  4. Use the Go Live process on the Developer Portal when ready for production.

What's the recommended migration path?

  1. Subscribe your existing app to the new products via the Subscriptions API:
    • payments-se-private (PIS scope)
    • payments-se-corporate (PIS scope)
  2. Update base URL from /openbanking/psd2/v1 to /openbanking/psd2/se/v2.
  3. Split your code paths by customer segment (private vs corporate).
  4. Map your product calls to the new endpoint structure (/single/{product} vs /signing-basket).
  5. Adapt payloads to the product-specific schemas — particularly creditorAccount.accountType, chargeBearer, paymentTypeInformation and remittance text patterns.
  6. Move SEK Credit Transfer flows into baskets — single sek-credit-transfer is not supported in v2.
  7. Re-test SCA flows — both DECOUPLED and REDIRECT are still supported, but URLs and link structures are versioned.
  8. Regression-test cancellation flows for future-dated payments.
  9. Verify in Sandbox, then complete Go Live on the Developer Portal.

Which payment products are supported in the new Swedish APIs?

ProductCode (URL identifier)PrivateCorporate
Handelsbanken Domestic Credit Transfer (SHBCT)handelsbanken-domestic-credit-transfer
SEK Credit Transfer (SEKCT) — former bank transfers and giro paymentssek-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?

EnvironmentURL
Sandboxhttps://sandbox.handelsbanken.com/openbanking/psd2/se/v2
Livehttps://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/{segment}/single/{paymentProduct} — single corporate payments
  • /payments/{segment}/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 productNew SE v2.0.3 product
swedish-domestic-giro-paymentsek-credit-transfer (SEKCT, basket only)
swedish-domestic-credit-transfersek-credit-transfer (SEKCT, basket only)
sepa-credit-transfersepa-credit-transfer (Corporate only)
cross-currency-credit-transfercross-currency-credit-transfer
(new in v2)other-banks-domestic-credit-transfer (OBDCT)
(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.

Which payment products require a signing basket, and which use single payment execute?

Which payment products require a signing basket, and which use single payment execute?

Every payment product uses one flow or the other. No product supports both, and the two flows cannot be mixed within one payment.

ProductTokenFlowPrivateCorporate
Handelsbanken Domestic Credit Transfer (SHBCT)handelsbanken-domestic-credit-transferSingleYesYes
Other Bank Domestic Credit Transfer (OBDCT)other-bank-domestic-credit-transferSingleYesYes
Cross Currency Credit Transfer (CCCT)cross-currency-credit-transferSingleYesYes
SEPA Credit Transfer (SEPACT)sepa-credit-transferSingleNoYes
SEK Credit Transfer (SEKCT)sek-credit-transferSigning basketYesYes

Single payment flow — SHBCT, OBDCT, CCCT and SEPACT

  1. POST /payments/{segment}/single/{paymentProduct} — returns a paymentId.
  2. Authorise using the paymentId.
  3. PUT /payments/{segment}/single/{paymentProduct}/{paymentId} — executes the payment.

Signing basket flow — SEKCT only

  1. POST /payments/{segment}/signing-basket — returns a signingBasketId and a payments array.
  2. Authorise using the signingBasketId.
  3. PUT /payments/{segment}/signing-basket/{signingBasketId} — executes the basket.

The single payment execute endpoint does not accept sek-credit-transfer as a product token, and the signing basket request body accepts only the sekCreditTransfers array. The specification enforces the separation, so a product cannot be routed through the wrong flow.

A basket containing one payment is valid and supported. If you need to send an individual SEK Credit Transfer, send a sekCreditTransfers array of length 1 — you do not need to batch payments to use this product.

The identifier differs between the two flows: single payments are driven by paymentId throughout, baskets by signingBasketId throughout. Mixing them is the most common cause of authorisation failures. See Why must I use the signing basket reference throughout the whole flow for detail.

How have signing baskets changed?

AspectLegacy v1.3.2New v2.0.3
EligibilitySweden, individuals, swedish-domestic-giro-payment onlyBoth Private and Corporate, sek-credit-transfer
Max payments per basket3030 (Private) / 100 (Corporate)
Min payments per basket11

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 PaymentPayload for all products.
  • creditorAccount.accountType is now strictly enumerated per product:
    • SHBCTBBAN only (8–9 digit Swedish SHB account number)
    • SEKCTBBAN, IBAN (Swedish IBANs only), PG, BG
    • SEPACT, CCCT Nordic, CCCT EU-paymentIBAN only
    • CCCT Normal / ExpressIBAN or BBAN
  • chargeBearer values are now tied to product:
    • SHAR — SEPACT, CCCT Nordic, CCCT EU-payment
    • CHAR, CRED, DEBT — CCCT Normal and Express
  • PaymentTypeInformation uses ISO 20022 codes (SEPA, URGP, NURG) and category-purpose codes (SUPP, NOPA, EUSE).
  • Stricter character patterns apply to creditor.name, remittanceInformation.text and 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?

Yes

Is SEK Credit Transfer available only as a basket payment?

Yes. This applies to both the Payments SE Private API and the Payments SE Corporate API. There is no single-payment initiation endpoint for SEK Credit Transfer in either API — neither /payments/private/single/sek-credit-transfer nor /payments/{segment}/single/sek-credit-transfer exists. This is by design and is not an omission in the specification.

SEK Credit Transfers are initiated only through the signing basket:

  1. Private:POST /payments/private/signing-basket
  2. Corporate:POST /payments/{segment}/signing-basket

The request body has a single required property, sekCreditTransfers, which is an array of payment objects. The Private API accepts a minimum of 1 and a maximum of 30 payments per basket; the Corporate API accepts a minimum of 1 and a maximum of 100.

A basket containing a single payment is correct and fully supported. An array of length 1 is the intended way to initiate an individual SEK Credit Transfer — you do not need to batch payments to use this product.

The sek-credit-transfer product token is still used, but only for operations that follow initiation:

  1. Status: GET /payments/{segment}/sek-credit-transfer/{paymentId}/status
  2. Cancellation: POST /payments/{segment}/sek-credit-transfer/{paymentId}/cancellation-authorizations

Seeing the token in these paths is the most common reason TPPs expect a matching initiation endpoint to exist.

Implementation note: the signingBasketId returned by the initiation call must be used consistently across the authorization request, the token exchange and the basket execution call (PUT /payments/{segment}/signing-basket/{signingBasketId}). Substituting a paymentId at any of these steps is the most frequent cause of failures in this flow.

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/{segment}/signing-basket) returns a 201 containing:

  1. One signingBasketId — the identifier for the basket as a whole.
  2. A payments array, where each individual payment in the basket has its own paymentId and tppPaymentReference.

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. The paymentId values 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

  1. Initiate the basket. Call POST /payments/private/signing-basket. Read the signingBasketId from the response and keep it.
  2. 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.
  3. 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 a paymentId at this point.
  4. Execute the basket. Call PUT /payments/private/signing-basket/{signingBasketId}, using the same signingBasketId as the path parameter and the token from step 3.

The same four steps apply to the Corporate API using the /payments/{segment}/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:

  1. A 401 or 403 at the authorisation or token step, because the token is not valid for the resource being addressed.
  2. A PUT to the basket that fails because the token was bound to a payment, not to the basket.
  3. 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

  1. Am I storing the signingBasketId from the initiation response, and not a paymentId?
  2. Is my authorisation redirect bound to the signingBasketId?
  3. Is the access token I use for execution the one obtained for that signingBasketId?
  4. Does my PUT path 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 per-payment endpoint GET /payments/{segment}/{paymentProduct}/{paymentId}/status with paymentProduct = sek-credit-transfer and the paymentId returned for each payment in the initiation response.

The initiation response returns a payments array, each element carrying its own paymentId together with your tppPaymentReference. Use that mapping to poll each payment individually.

The signingBasketId is not accepted by the status endpoint. It is used for authorisation and for basket execution only.

Other Banks Domestic Credit Transfer (OBDCT)

Other Bank Domestic Credit Transfer (OBDCT) is a single payment in SEK to an account held at another Swedish bank. The customer's account is debited immediately once the payment has passed all internal validations, and the payment is then processed in the next possible clearing cycle.

Use this product when it matters to the customer that the money leaves their account now rather than on a chosen date — for example when releasing goods, settling a deposit, or confirming a transaction at the point of sale.

Choosing between OBDCT and SEK Credit Transfer

SEK Credit Transfer (SEKCT) remains the main product for payments to other banks, and is the right choice in most cases. It supports a 140-character reference text, creditor accounts given as IBAN, Bankgiro or Plusgiro, and a chosen execution date.

OBDCT trades that flexibility for immediate debiting. Its reference text is limited to 12 characters, both accounts must be given as BBAN, and no execution date can be supplied. Choose OBDCT only when the timing of the debit is the deciding factor.

Endpoint

  1. Private: POST /payments/private/single/other-bank-domestic-credit-transfer
  2. Corporate: POST /payments/corporate/single/other-bank-domestic-credit-transfer

Conditions for OBDCT

  1. The currency must be SEK. No other currency is accepted.
  2. The payment must be initiated as a single payment. It cannot be included in a signing basket.
  3. No execution date can be supplied in the request. Because the payment is debited immediately after passing internal validations, the execution date is set automatically.
  4. Both the debtor and the creditor account must be sent as BBAN. This is the only account type accepted for either party; IBAN, Bankgiro and Plusgiro are not supported.
  5. The debtor account is a Handelsbanken account number of 8 or 9 digits.
  6. The creditor account number is 7 to 10 digits.
  7. The creditor clearing number must belong to another bank. Handelsbanken clearing numbers 6000–6999 are not accepted; use Handelsbanken Domestic Credit Transfer (SHBCT) for accounts held at Handelsbanken.
  8. The amount must be at least 1 SEK, with a maximum of 10 integer digits and 2 decimals.
  9. The payment cannot be cancelled once initiated.
  10. Payments to other banks cannot be initiated from an Allkortskonto.

Fields that differ from SEK Credit Transfer

If you are adapting a working SEK Credit Transfer payload, the following differences are the most frequent causes of an HTTP 400 on this product.

  1. debtorAccount.text is mandatory for OBDCT, maximum 14 characters. There is no equivalent field in SEK Credit Transfer, so it is easily missed.
  2. creditorAgent.identification.code accepts digits only, either four or five of them, and must not begin with 6. A hyphen is not permitted. SEK Credit Transfer does accept a hyphen in this field, so a clearing number that you send as 8327-9 for SEKCT must be sent as 83279 for OBDCT.
  3. creditorAgent.identification.type is mandatory alongside code.
  4. creditor.name is mandatory, maximum 35 characters.
  5. remittanceInformation.text is mandatory, maximum 12 characters. This is the shortest beneficiary reference of any Swedish payment product, so check that your reference format fits before migrating a flow to OBDCT.

Free-text fields follow the character patterns defined in the API specification. Characters outside those sets will be rejected at validation.

Response and payment status

A successful initiation returns a paymentId together with transactionStatus of ACTC and the available SCA methods. Complete the authorisation as for any other single payment.

Payment status is retrieved through GET /payments/{segment}/other-bank-domestic-credit-transfer/{paymentId}/status, which returns ISO 20022 status codes. Where a payment is rejected, the response also carries reasonCode and reason giving the specific cause.

The product token other-bank-domestic-credit-transfer is valid on the status endpoint but not on the cancellation endpoint, as this product cannot be cancelled. Status can still be retrieved for a payment that has already been debited.

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

  1. Execute basket. If transactionStatus = PARTIAL_FAILURE,
  2. Iterate the payments array from the initiation response,
  3. Call GET .../sek-credit-transfer/{paymentId}/status for each, treating RJCT + reasonCode/reason as 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.

  1. 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.
  2. 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?