NAV
Curl

Getting started

The ZTL API API is modular, with the following areas of functionality:

Credentials

To call the API you need:

API Description

Api examples are provided along the description of each section. The below documentation only refers to v1.

We are currently working on a new version on our APIs, but only v1 is officially supported:

Error handling

Error handling follows HTTP error codes as closely as possible. In general meaning:

Environments

The following is a list of environments available when developing against or using our API:

Resource Sandbox Prod
API Endpoint https://api.sandbox.ztlpay-test.io https://api.ztlpay.io
Authentication endpoint https://oidc.sandbox.ztlpay-test.io https://oidc.ztlpay.io

Testing in Sandbox Environment

We refer to banks sandbox documentation for updated info about valid test data.

Bank Documentation Verified
DNB https://developer.dnb.no/api-explorer ✔️
Nordea https://openbanking.direct.nordea.no/portal-sandbox/gettingstarted
Sbanken https://openbanking.sbanken.no/portal-sandbox/documentation

While some banks above are not verified, some functionality works. Depending on your use-case you can test and see. Account services usually work for all of them.

Glossary for ZTL API

These are terms used in ZTL API:

General

PSD2 providers and services

Authentication with ZTL

Access Token

Access tokens are used in token-based authentication which is what most of our API endpoints are hidden behind. This means you are required to obtain an access token to gain access to these endpoints

curl --request POST \
  --url 'https://oidc.sandbox.ztlpay-test.io/connect/token' \
  --header 'content-type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials' \
  --data 'client_id=YOUR_CLIENT_ID' \
  --data 'client_secret=YOUR_CLIENT_SECRET' \
  --data 'scope=payments'

To get a new access token you need to provide your client with the following information:

Using the Token

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/ENDPOINT \
  -H 'Authorization: Bearer ACCESS_TOKEN'

Access tokens should always be sent in the header of every HTTP request going to our API. Simply set the Authorization header to Bearer ACCESS_TOKEN to gain access to our endpoints

Trying to send a request without a correct or valid access token will result in most requests returning a 401 response code

Onboarding

Starting an onboarding

# Start onboarding example request
curl -X POST \
  https://api.sandbox.ztlpay-test.io/api/onboarding \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -d '{ "organizationId": "123456789", "webhookUrl": "https://example.com", "redirectUrl": "https://example.com" }'

Make a POST request to https://api.sandbox.ztlpay-test.io/api/onboarding to start an onboarding.

The request object must conform to the schema shown in the example.

Body description

Start onboarding example request body
{
  "type": "object",
  "description": "Start onboarding",
  "definitions": {},
  "required": ["organizationId"],
  "properties": {
    "organizationId": {
      "$id": "#/properties/organizationId",
      "type": "string",
      "examples": ["12341234"],
      "pattern": "^(.*)$"
    },
    "webhookUrl": {
      "$id": "#/properties/webhookUrl",
      "type": "string"
    },
    "redirectUrl": {
      "$id": "#/properties/redirectUrl",
      "type": "string"
    }
  }
}

Response

Please note that both http response code 200 and 201 are possible: - 200 returned if there is an existing onboarding process for the company from before (flowId of the initial onboarding is returned) - 201 is returned on a new onboarding process request

Start onboarding example response
{
  "flowId": "d293b808-8adf-4280-9449-8bf52d49795b",
  "onboardingUrl": "https://onboarding.sandbox.ztlpay-test.io/onboarding/d293b808-8adf-4280-9449-8bf52d49795b"
}

Onboarding Expiration and canceled flows:

An onboarding process is persisted for an undetermined time, but no less than 45 days. However, processes which are pending signing are forcefully expired after 45 days. Upon expiration the process is forcefully canceled. Canceled processes cannot be resumed. If the process is expired then feel free to initiate a new one.

Status API

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/onboarding/d293b808-8adf-4280-9449-8bf52d49795b/status \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

The status of a onboarding can be retrieved via the status endpoint. GET request to https://api.sandbox.ztlpay-test.io/api/onboarding/d293b808-8adf-4280-9449-8bf52d49795b/status:

It should be made clear that both a process and its tasks can have status indicators. These values conform to the kebab-casing convention (lowercase words separated by hyphens).

Tasks

A single task can have the following status codes:

Status

Example response, successful onboarding
{
  "orgName": "Ztl",
  "orgNo": "123456789",
  "status": "completed",
  "tasks": [],
  "redirectUrl": "https://example.com",
  "webhookStatuses": {
    "complete": {
        "status": "success",
        "timestamp": "2021-12-16T18:19:55.267Z"
    }
  }
}
Example response, terminated by case worker
{
    "orgName": "HVOSLEF AS",
    "orgNo": "816812542",
    "status": "archived",
    "tasks": [],
    "redirectUrl": "https://demo.dev-2.ztlpay.io/onboarding",
    "webhookStatuses": {
        "complete": {
            "status": "not-provided",
            "timestamp": "2021-12-29T15:23:10.989Z"
        }
    },
    "archivedBy": {
        "role": "case-worker"
    }
}
Example response, onboarding terminated by user
{
  "orgName": "Ztl",
  "orgNo": "123456789",
  "status": "archived",
  "tasks": [],
  "redirectUrl": "https://example.com",
  "webhookStatuses": {
    "complete": {
        "status": "pending",
        "timestamp": "2021-12-16T18:19:55.267Z"
    }
  },
  "archivedBy": {
    "role": "unknown"
  }
}

A single process always has a status, represented as an exact string. Please note that new values for status may be added in the future. Possible statuses are listed below, divided into four conceptual categories:

Active processes

A process with any of these statuses is active/ongoing.

Successful processes

Unsuccessful processes

A process with any of these statuses is considered declined or rejected, and cannot be restarted or otherwise progressed, even by a caseworker. The end user will have to start a new onboarding from scratch.

Canceled processes

webhookStatuses

POST payload for completed webhook
{
    "eventType": "Completed",
    "entityName": "Onboarding",
    "entityId": "flowId",
    "entity": {
      "organizationId"
    },
    "timeStamp": "new Date().toISOString()"
}

webhookStatuses is a collection of "webhookResponse" objects which contains various information about a provided webhook. E.g: The "complete" webhook, depicted in the JSON example, contains a status of the provided webhook (success) and a timestamp of the webhookResponse objects instantiation. All webhookResponse objects have two properties: status, and timestamp.

A webhook's status is always one of the four values mentioned below:

Note that an onboarding will still continue its flow, regardless of any webhookResponse object's status that is associated with the application. The list of possible webhook objects can potentially be expanded upon in the future, however there is currently only a "complete" webhook in use. Depicted below, is the POST requests payload for the "completed" webhook URL post request.

Final notes

Phone Numbers And E-mail Messages

We send real messages via sms and email to the specified recipient during an onboarding. This is true for development and test environments as well. Be aware that this could have unfortunate side effects should you use random people's contact information during testing.

Data quality in sandbox environment

The data quality in this environment may at times be lacking or outright missing due to missing data from our providers.

Known cases: 1. Data about Ultimate Beneficial Owners(UBO) is unavailable. Feel free to add your own UBO entries via the Know Your Customer(KYC) form.

Consent

Consent is given by the ERP's end-user (customer) by means of strong customer authentication (SCA).

ZTL Payment Solution requires the end-user to give consent for payment and AISP services. The end-user's bank is handling the actual strong customer authentication through BankID. During the process, a consent-token is generated and passed back to the ERP system. The consent should be stored by the ERP system and can be used as long as it is valid (90 days).

Even though the consent has an expiry date, it is not guaranteed to be valid until that date. Banks can cancel and re-issue consents for a multiple of reasons (usually security). So please make sure to handle expired consent errors in all APIs with an issuing of a new consent.

curl -X POST \
  https://api.sandbox.ztlpay-test.io/api/consents \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: "123123123"' \
  -H 'PSU-IP-Address: 192.158.1.38' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
  -d '{ "accountOwnerSsn": "{SSN}",
        "accountOwnerOrganizationReference": "{Organization Number}",
        "callbackUrl": "https://www.google.com",
        "debtorBankBic": "DNBANOKK",
        "instructionId": "ca8c24ad-740b-464b-8cfb-85fe4ae2ca44"
      }'
// Example consent response 200
{
  "scaUrl": "https://lldkit7c6f.execute-api.eu-west-1.amazonaws.com/Prod/bankid/logon?blob=eyJ1c2VySWQiOiIzMTEyNTQ2NDM0NiIsInJlZGlyZWN0VXJsIjoiaHR0cHM6Ly9zYW5kYm94Lm5hYXMtdGVzdDEubmV0cy5ldS9kbmJhbm9ray9jb25zZW50L2FjY2VwdC80OTJjYWZhYzJiYzY0ODliOTRkM2M4YWVkZTNhNmYyMyIsIm1vZGUiOiJQcm9kIiwiY29uc2VudElkIjoiZGJhNjk2ZTgtMTA0NC00NzBiLTkyZGQtNmQwZTI2YTcwZTYzIiwicHNkMkVuZHBvaW50IjoiaHR0cHM6Ly9pbnRlcm5hbC1zYW5kYm94LXNoYXNsLWFsYi05MTg3MDk3MDUuZXUtd2VzdC0xLmVsYi5hbWF6b25hd3MuY29tL3YxL25ldGJhbmsvcHJveHkvYXV0aGVudGljYXRpb24vdXBkYXRlc3RhdHVzIiwiY29uc2VudEVuZHBvaW50IjoiaHR0cHM6Ly9pbnRlcm5hbC1zYW5kYm94LXNoYXNsLWFsYi05MTg3MDk3MDUuZXUtd2VzdC0xLmVsYi5hbWF6b25hd3MuY29tL3YxL25ldGJhbmsvcHJveHkvYXV0aG9yaXNhdGlvbi9kYmE2OTZlOC0xMDQ0LTQ3MGItOTJkZC02ZDBlMjZhNzBlNjMiLCJwYXltZW50SWQiOiJBSVN8ODMzNjIwMWYtYzY2OS00ZTFkLWIxOGEtNjhlN2EzZTc0MjY4IiwidHBwIjoiUFNETk8tVFNUTkNBLTQ1NTIzMzk3fE5ldHMgTm9yd2F5IiwidGltZXN0YW1wIjoiMTU3NjA3MDYxNzY0MiJ9&redirectUrl=https%3A%2F%2F%2Fdnbanokk%2Fconsent%2Faccept%2F492cafac2bc6489b94d3c8aede3a6f23&la",
  "consentAuthorizationId": "492cafac2bc6489b94d3c8aede3a6f23",
  "status": "AuthorizationRequired",
  "reason": null,
  "expirationDate": "2020-03-10T14:23:36.57+01:00"
}

ZTL Payment Solution exposes an endpoint that the ERP can use to let its users create consent-tokens:

Headers description

Body description

The ERP has to provide four fields to ZTL, which will be used to create the token at the customer's bank:

Strong Customer Authentication

The scaUrl in the response will take the user to the banks's portal for Strong Customer Authentication. This will allow the end user to create a consent-token using one of the methods suppored by the bank, e.g.:

The SCA can be either decoupled or embedded, depending on the bank's implementation. The bank also controls how the created consent-token is sent back to the PSD2 aggregator.

Going to the scaUrl in the current test-environment will direct the user to the PSD2 aggregators example SCA page, in this case DNB. To authorise, simply press the button labeled with Fortsett which will redirect the user to the callback URL specified in the initial request. The naasConsentReference will be added to the redirect URL as a parameter and formatted similar to this:

https://www.google.com/?instructionId=HEyKZacHyZ&naasConsentReference=eb42984d-4d7d-4d50-829e-6b531501176b&sca=success

The ERP vendor has to store both the consentAuthorizationId and the naasConsentReference for further processing. naasConsentReference will be used as the consentReference in further API-calls.

For BankID testing you can find a table with valid personal identification numbers that you may use on this page (in Norwegian).

consentAuthorizationId should be used to check the concent status.

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/consents?consentAuthorizationId={consentAuthorizationId} \
  -H 'Authorization: Bearer {access-token}'

Consent status response body json

{ "authorizationType": "RECURRING_PIS,RECURRING_AIS,RECURRING_CAF", "consentState": { "aisExpired": false, "cafExpired": false, "pisExpired": false }, "expirationDate": "2020-05-07T10:03:55.479+02:00", "reason": null, "status": "Accepted" } ```

Note that the reason field will be null most of the time unless a REJECTED status is fetched from the Aggregator. Known status for consents are :

Revoking the consent invalidates the consent and prevents further operations with the consent.

curl -X DELETE \
  https://api.sandbox.ztlpay-test.io/api/consents \
  -H 'Consent-Reference: {consentAuthorizationId}' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'

Account Information

Retrieve the current available and booked balance for the account specified in the authorized query.

Account info

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/accounts/info?consentAuthorizationId={consentAuthorizationId}&consentReference={consentReference} \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Example response for account info
{
  "accounts": [ {
    "id": "12043175449",
    "name": "Brukskonto",
    "bic": "AABASESSXXX",
    "bban": "12043175449",
    "iban": "NO0812043175449",
    "country": "NO",
    "currency": "NOK",
    "type": "Debit"
  } ]
}

With query params: consentAuthorizationId and consentReference from Create Consent-step. In Sandbox, the account_id 12043175449 may be used for querying balance.

Account balance

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/accounts/{accountId}/balance?consentAuthorizationId={consentAuthorizationId}&consentReference={consentReference} \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Example response for account balance
{
  "id": "12043175449",
  "name": "Brukskonto",
  "bic": "AABASESSXXX",
  "bban": "12043175449",
  "iban": "NO0812043175449",
  "country": "NO",
  "currency": "NOK",
  "type": "Debit",
  "balance": {
    "booked": {
      "amount": "123.34",
      "currency": "NOK"
    },
    "available": {
      "amount": "123.34",
      "currency": "NOK"
    }
  }
}

You can query single account balance.

Account entries

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/accounts/{accountId}/entries?
    consentAuthorizationId={consentAuthorizationId}&consentReference={consentReference}&fromDate={fromDate}&toDate={toDate} \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Example Response for account entries
{
  "account": {
    "id": "12043175449",
    "name": "Brukskonto",
    "bic": "AABASESSXXX",
    "bban": "12043175449",
    "iban": "NO0812043175449",
    "country": "NO",
    "currency": "NOK",
    "type": "Debit"
  },
  "accountEntries": [
    {
      "transactionId": "121111112",
      "type" : "Debit",
      "status" : "Booked",
      "bookingDateTime" : "2020-10-06T22:00:00Z",
      "valueDateTime" : "2020-10-06T22:00:00Z",
      "postedAmount" : {
        "amount" : 1.0,
        "currency" : "NOK"
      },
      "debtor": {
        "name": "Jan Johansen",
        "bban": "",
        "iban": ""
      },
      "creditor": {
        "name": "Jan Johansen",
        "bban": "",
        "iban": ""
      },
      "purposeText": "Overføring Innland",
      "remittanceInformation": {
        "reference": "313123121231",
        "unstructured": "Fra Avsender Pål Pålsson"
      },
      "endToEndId" : "100543943"
    }
  ]
}

You can query single account entries.

The query params fromDate and toDate are optional and inclusive, and uses the ISO-8601 standard for date. If omitted, the ASPSP may revert to a default date range. The maximum date range can be retrieved from the Supported Banks-endpoint.

The amount in postedAmount is always positive, and its the type of the entry (i.e., debit or credit) that informs whether the entry is for an outgoing- or incoming amount.

Clarification regarding header values

According to the official PSD2 regulations, the payment service user (PSU, end user) can fetch their own account information as often as they like without requiring a new SCA/authentication. However, other parties, such as ERP-systems, are limited to 4 retrievals every 24 hours when fetching account information on behalf of the PSU, without the PSU being present.

The presence of the PSU is determined by the information provided in the header fields user_present, PSU-Geo-Location, PSU-IP-Address and PSU-User-Agent; these PSU values are all used when signing requests. When the limit of unattended requests has been reached, further requests will receive an error-response with error code 429.

Payments

Overview

Domestic and international payments are supported.

Payments Overview

In short, the payment process consist of the following logical steps:

1. User Input and Validation Using methods to validate payment transactions before initiating

2. Payment Initiation Create the payment job.

3. Status (Pre SCA) Exchange rate and total cost for international transactions should be displayed to end-user before the user goes to SCA signing.

4. SCA End user needs to sign SCA.

5. Status Transaction status updates are available through the status endpoint.

6. Settled The transaction end state, where the money has been transferred to the beneficiary account.

User Input/Validation

User Input and Validation

The payment content is validated before the payment process is created, and the payment process will not be created in case of validation errors.

The following error situations may occur:

Error Description
400 urn:pain-invalid Error validating pain xml content. Indicates syntax/validation error of xml, or content error
400 urn:missing-header Required header not set in request (See detail)

Validation errors

If the payment request fails ZTL's internal validation, a list of validation errors is provided as a part of the response.

Validation error example
  {
    "code": "urn:max-length-exceeded",
    "path": "CstmrCdtTrfInitn.PmtInf[0].Dbtr.PstlAdr",
    "message": "Sender's address is too long",
    "developerText": "Debtor address line 'Joveien 45 8036 Bodo Norway' with length 27 is longer than the maximum allowed length 20 for debtor agent DNBANOKK"
  }

Most of the validation errors are assigned a distinct code for easier error handling and better end-user experience, following table describes possible error codes:

Code Description
urn:number-of-transactions-invalid Provided total number of transactions in a payment job does not match actual number of transactions
urn:urn:basket-size-exceeded Number of transactions in payment request exceeds debtor bank limitation
urn:sum-mismatch Provided control sum of transaction amounts in a payment job does not match actual sum of transaction amounts
urn:organization-account-mismatch Provided organization ID does not own the debtor account
urn:end-to-end-id-not-unique One or several transaction endToEndIDs are used more than one time in the same payment job
urn:country-missing Country code is missing from creditor/debtor (specified in 'path' field) postal address
urn:max-length-exceeded The value in the field provided under 'path' in error object has exceeded maximum length
urn:amount-invalid Transaction amount exceeds the limit for given currency
urn:bic-missing BIC is missing from creditor/debtor (specified in 'path' field) account information
urn:bic-unknown Provided BIC in creditor/debtor (specified in 'path' field) account information could not be matched to a bank or
BIC could not be derived from the provided account number
urn:remittance-info-invalid Remittance info is of invalid format (bank specific)
urn:invalid-characters The value in the field provided under 'path' in error object contains invalid characters
urn:due-date-invalid Provided requested execution date is in the past
urn:due-date-non-business-day Provided requested execution date is a non-banking day
urn:creditor-info-invalid Creditor information doesn't match the requirements based on provided country/currency/bank
urn:iban-invalid Provided creditor/debtor (specified in 'path' field) IBAN is invalid for given country
urn:bban-invalid Provided creditor/debtor (specified in 'path' field) BBAN is invalid
urn:iban-or-bban-missing Provided creditor/debtor (specified in 'path' field) account information is missing account number
urn:kid-invalid Provided KID in not valid in MOD10/MOD11 or exceeds maximum length of 25 characters
urn:invalid-remittance-info KID cannot be sent on international payments
urn:bic-iban-mismatch Provided combination of BIC and IBAN is not valid
urn:debtor-post-code-invalid Debtor address is set to Norway, but the given postal code is not a valid Norwegian postal code
urn:creditor-post-code-invalid Creditor address is set to Norway, but the given postal code is not a valid Norwegian postal code
urn:company-disabled-for-international-payments Company is not enabled for international payments. ZTL will contact user for such enablement

The following API gives useful information about supported currencies, banks and constraints, and should be used to ensure that valid transactions are created.

Supported currencies

Example of a non-complete response of supported currencies
[
  {
    "code": "EUR",
    "currencyName": "Euro",
    "isoScale": 2,
    "symbol": "€"
  },
  {
    "code": "ISK",
    "currencyName": "Icelandic króna",
    "isoScale": 0,
    "symbol": "kr"
  },
  {
    "code": "USD",
    "currencyName": "United States dollar",
    "isoScale": 2,
    "symbol": "$"
  }
]

Supported currencies shows a list of available currencies for international payments. It also provides currency-specific constraints, e.g. required creditor bank information/format. Note that the only currency supported for payments to Norwegian bank accounts is NOK.

Available due dates

Available due dates for a transaction are dependent on the currency and amount. It's recommended to get a list of available due dates before initiating payments, this is especially important for international transactions.

Available banks

Example of supported bank response
[ {
  "bic" : "DNBANOKK",
  "constraints" : {
    "maxBasketSize" : "20",
    "maxInformationFieldLength" : "35",
    "maxUnstructuredRemittanceInfoLength" : "108",
    "aisMaxDateRangeMonths" : "3",
    "aisMaxDateRangeNote" : "supports up to 24 months if consent age < 1h",
    "cancelSCARequired": "false",
    "validInformationFieldCharactersPattern": "[A-ZÆØÅa-zæøå0-9\\D]"
  },
  "countryCode" : "NO",
  "name" : "DNB"
}, {
  "bic" : "HANDNOKK",
  "constraints" : {
    "maxBasketSize" : "10",
    "maxInformationFieldLength" : "35",
    "maxUnstructuredRemittanceInfoLength" : "128"
  },
  "countryCode" : "NO",
  "name" : "Handelsbanken"
}, {
  "bic" : "NDEANOKK",
  "constraints" : {
    "maxBasketSize" : "10",
    "maxInformationFieldLength" : "30",
    "maxUnstructuredRemittanceInfoLength" : "128",
    "aisMaxDateRangeMonths" : "12"
  },
  "countryCode" : "NO",
  "name" : "Nordea"
} ]

The Supported banks lists all supported bank. Due to variations in the PSD2 APIs of the different banks, initiating a payment with certain banks may be subject to some limitations. This service also shows the constraints per bank, it is strongly recommended using the constraint information to validate user input for both Payment- and AISP-services.

Initiate Payment

Example request for initiating payment
curl -X POST \
  https://api.sandbox.ztlpay-test.io/api/payments \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'PSU-IP-Address: 192.158.1.38' \
  -d '{
        "organizationId": "123456789",
        "countryCode": "NO",
        "scaRedirectUrl": "https://example.com/callback",
        "idempotencyKey": "123123123",
        "consentReference": "1235667-aojsadpjsa-1021903-aojs",
        "base64Pain001": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiP...mZJbml0bj4NCjwvRG9jdW1lbnQ+DQo="
      }'
Example response body for initiated payment
{
  "flowId": "c766e7e0-ea15-4b4a-9574-a27c763f6c17",
  "statusUrl": "https://api.sandbox.ztlpay-test.io/api/payments/c766e7e0-ea15-4b4a-9574-a27c763f6c17/status"
}

Initiate Payment

A new payment with one or more transaction can be created by initiating payment. The successful response contains a link to the payment status, this url should be used for both retrieving updated payment status and to get payment information like SCA url and total cost.

Pain format

Initiate payment uses the Payment Initiation V03 format, xml schema.

Example Pain Xml with single transaction
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
    <CstmrCdtTrfInitn>
        <GrpHdr>
            <MsgId>20211217000000-228628058026</MsgId>
            <CreDtTm>2021-12-17T15:18:25.925843</CreDtTm>
            <NbOfTxs>1</NbOfTxs>
            <CtrlSum>12.00</CtrlSum> <!-- Optional -->
            <InitgPty></InitgPty> <!-- Can be left empty, but the element is required -->
        </GrpHdr>
        <PmtInf>
            <PmtInfId>20211217000000-228628058026-1</PmtInfId>
            <PmtMtd>TRF</PmtMtd> <!-- The value is ignored, but the element is required -->
            <NbOfTxs>1</NbOfTxs> <!-- Optional -->
            <CtrlSum>12.00</CtrlSum> <!-- Optional -->
            <ReqdExctnDt>2021-12-17</ReqdExctnDt>
            <Dbtr>
                <Nm>Test company AS</Nm>
                <PstlAdr>
                    <StrtNm>Solheimroa 4</StrtNm>
                    <PstCd>9883</PstCd>
                    <TwnNm>Evensen</TwnNm>
                    <Ctry>NO</Ctry>
                </PstlAdr>
            </Dbtr>
            <DbtrAcct><Id><Othr><Id>12043175449</Id><SchmeNm><Cd>BBAN</Cd></SchmeNm></Othr></Id></DbtrAcct>
            <DbtrAgt><FinInstnId><BIC>DNBANOKK</BIC></FinInstnId></DbtrAgt>
            <CdtTrfTxInf>
                <PmtId><EndToEndId>762362604515896127385745121302</EndToEndId></PmtId>
                <Amt><InstdAmt Ccy="EUR">12.00</InstdAmt></Amt>
                <CdtrAgt><FinInstnId><BIC>INGDDEFFXXX</BIC></FinInstnId></CdtrAgt>
                <Cdtr><Nm>EUR Company Inc</Nm><PstlAdr><Ctry>NO</Ctry></PstlAdr></Cdtr>
                <CdtrAcct><Id><IBAN>DE38500105175114522294</IBAN></Id></CdtrAcct>
                <Purp><Cd>OTHR</Cd></Purp> <!-- Optional, defaults to <Cd>OTHR</Cd> -->
                <RmtInf><Ustrd>Testing</Ustrd></RmtInf>
            </CdtTrfTxInf>
        </PmtInf>
    </CstmrCdtTrfInitn>
</Document>
Example Pain Xml with two international transactions, first with unstructured remittance info, second with KID
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
    <CstmrCdtTrfInitn>
        <GrpHdr>
            <MsgId>20220302000000-521206628232</MsgId>
            <CreDtTm>2022-03-02T09:34:52.878633341</CreDtTm>
            <NbOfTxs>2</NbOfTxs>
            <CtrlSum>30.00</CtrlSum>
            <InitgPty></InitgPty>
        </GrpHdr>
        <PmtInf>
            <PmtInfId>20220302000000-521206628232-1</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <NbOfTxs>1</NbOfTxs>
            <CtrlSum>12.00</CtrlSum>
            <ReqdExctnDt>2022-03-02</ReqdExctnDt>
            <Dbtr>
                <Nm>Test company AS</Nm>
                <PstlAdr>
                    <StrtNm>Solheimroa 4</StrtNm>
                    <PstCd>9883</PstCd>
                    <TwnNm>Evensen</TwnNm>
                    <Ctry>NO</Ctry>
                </PstlAdr>
            </Dbtr>
            <DbtrAcct><Id><Othr><Id>12041111111</Id><SchmeNm><Cd>BBAN</Cd></SchmeNm></Othr></Id></DbtrAcct>
            <DbtrAgt><FinInstnId><BIC>DNBANOKK</BIC></FinInstnId></DbtrAgt>
            <CdtTrfTxInf>
                <PmtId><EndToEndId>564872588722933892663744621782</EndToEndId></PmtId>
                <Amt><InstdAmt Ccy="DKK">12.00</InstdAmt></Amt>
                <CdtrAgt><FinInstnId><BIC>CITIIE2X</BIC></FinInstnId></CdtrAgt>
                <Cdtr><Nm>DKK Company Inc</Nm><PstlAdr><Ctry>NO</Ctry></PstlAdr></Cdtr>
                <CdtrAcct><Id><IBAN>IE50CITI99005133791111</IBAN></Id></CdtrAcct>
                <Purp><Cd>OTHR</Cd></Purp>
                <RmtInf><Ustrd>Example Message</Ustrd></RmtInf>
            </CdtTrfTxInf>
        </PmtInf>
        <PmtInf>
            <PmtInfId>20220302000000-521206628232-2</PmtInfId>
            <PmtMtd>TRF</PmtMtd>
            <NbOfTxs>1</NbOfTxs>
            <CtrlSum>18.00</CtrlSum>
            <ReqdExctnDt>2022-03-02</ReqdExctnDt>
            <Dbtr>
                <Nm>Test company AS</Nm>
                <PstlAdr>
                    <StrtNm>Solheimroa 4</StrtNm>
                    <PstCd>9883</PstCd>
                    <TwnNm>Evensen</TwnNm>
                    <Ctry>NO</Ctry>
                </PstlAdr>
            </Dbtr>
            <DbtrAcct><Id><Othr><Id>12041111111</Id><SchmeNm><Cd>BBAN</Cd></SchmeNm></Othr></Id></DbtrAcct>
            <DbtrAgt><FinInstnId><BIC>DNBANOKK</BIC></FinInstnId></DbtrAgt>
            <CdtTrfTxInf>
                <PmtId><EndToEndId>259078089432288924472847479859</EndToEndId></PmtId>
                <Amt><InstdAmt Ccy="EUR">12.00</InstdAmt></Amt>
                <CdtrAgt><FinInstnId><BIC>CITIIE2X</BIC></FinInstnId></CdtrAgt>
                <Cdtr><Nm>EUR Company Inc</Nm><PstlAdr><Ctry>NO</Ctry></PstlAdr></Cdtr>
                <CdtrAcct><Id><IBAN>IE50CITI99005133791111</IBAN></Id></CdtrAcct>
                <Purp><Cd>OTHR</Cd></Purp>
                <RmtInf><Strd><CdtrRefInf><Tp><CdOrPrtry><Cd>SCOR</Cd></CdOrPrtry></Tp><Ref>123456789</Ref></CdtrRefInf></Strd></RmtInf>
            </CdtTrfTxInf>
        </PmtInf>
    </CstmrCdtTrfInitn>
</Document>

The following table shows an overview of the xml elements used by ZTL.

xml-element required description
GrpHdr.MsgId Y Must be unique max 35 char Id. May not contain special characters including ”å”, “ä” and “ö”
GrpHdr.NbOfTxs Y Number of total transactions in the xml document
GrpHdr.CreDtTm Y CreationDateTime. Assumed to be in timeZone "Europe/Oslo" unless it has an offset
GrpHdr.CtrlSum N Total of individual amounts included in the xml document, irrespective of the currency. Required.
GrpHdr.InitgPty Y InitiatingParty. Required xml element, but may be empty
GrpHdr.InitgPty.Nm N InitiatingParty Name
PmtInf Y There should be one PmtInf per transaction in a basket of payments
PmtInf.PmtInfId Y TransactionId. Must be unique max 35 char Id. May not contain special characters including ”å”, “ä” and “ö”
PmtInf.PmtMtd Y* The element is required by xml standard, but not used in the payment solution. Accepted values:
  • "CHK" - Cheque: Written order to a bank to pay a certain amount of money from one person to another person.
  • "TRF" - TransferAdvice: Transfer of an amount of money in the books of the account service. An advice should be sent back to the account owner.
  • "TRA" - CreditTransfer: Transfer of an amount of money in the books of the account service.
PmtInf.NbOfTxs N Number of total transactions in the group - should be 1. Optional, but validated if present.
PmtInf.CtrlSum N Total of all individual amounts included in the group, irrespective of the currency. Optional, but validated if present. Value should be equal to InstdAmt since we only allow one transaction per PmtInf
PmtInf.ReqdExctnDt Y Transaction Due date (from debtor account)
PmtInf.Dbtr.Nm Y Debtor Name
PmtInf.Dbtr.PstlAdr Y Debtor Address. Required for both domestic and international transactions. Minimum required fields: StrtNm, PstCd, TwnNm, Ctry
PmtInf.DbtrAcct.* Y Debtor account number. Can be BBAN or IBAN.
  • Bban example: xxxxxxxxxxx
  • Iban example: xxxxxxxxxxxBban
PmtInf.DbtrAgt.FinInstnId.BIC Y Debtor bank BIC.
PmtInf.CdtTrfTxInf.* Y CreditTransferTransactionInformation - There should only be one CdtTrfTxInf per PmtInf
PmtInf.CdtTrfTxInf.PmtId.EndToEndId Y Unique identification assigned by the initiating party to unambiguously identify the transaction. This identification is passed on, unchanged, throughout the entire end-to-end chain. Max 35char.
PmtInf.CdtTrfTxInf.Amt.InstdAmt Y Amount of money to be moved between the debtor and creditor, before deduction of charges, expressed in the currency as ordered by the initiating party. Ccy - Currency eg "NOK", "EUR", "SEK", "DKK"
PmtInf.CdtTrfTxInf.CdtrAgt.FinInstnId.BIC N* Creditor Bank BIC. Not mandatory for Norwegian domestic transactions - but recommended to include this information. Mandatory for international payments.
PmtInf.CdtTrfTxInf.CdtrAgt.FinInstnId.ClrSysMmbId.MmbId N Creditor Bank Clearing Code. Mandatory in some countries.
PmtInf.CdtTrfTxInf.Cdtr.* Y Creditor Identification. Note that Cdtr.Nm is required for AML reasons.
PmtInf.CdtTrfTxInf.Cdtr.PstlAdr.* Y Creditor Address. Required for both domestic and international transactions. Minimum required fields: StrtNm, Ctry for international transaction and valid PstCd for Norwegian creditors.
PmtInf.CdtTrfTxInf.CdtrAcct.* Y Creditor account number. Can be BBAN or IBAN.
  • Bban example:<CdtrAcct><Id><Othr><Id>xxxxxxxxxxx</Id><SchmeNm><Cd>Bban</Cd></SchmeNm></Othr></Id></CdtrAcct>
  • Iban example:<CdtrAcct><Id><IBAN>xxxxxxxxxxx</IBAN></Id></CdtrAcct>
PmtInf.CdtTrfTxInf.Purp.Cd N PurposeCode - Uses default OTHR if not present. Accepted values:
  • "INVS" - Investment And Securities
  • "SALA" - Salary Payment
  • "IDCP" - Irrevocable Debit Card Payment
  • "CASH" - Cash Management Transfer
  • "CORT" - Trade Settlement Payment
  • "INTC" - Intra Company Payment
  • "PENS" - Pension Payment
  • "SSBE" - Social Security Benefit
  • "SUPP" - Supplier Payment
  • "TAXS" - Tax Payment
  • "TREA" - Treasury Payment
  • "VATX" - Value Added Tax Payment
  • "LOAN" - Loan
  • "OTHR" - OtherUsed default value OTHR if not present.
PmtInf.CdtTrfTxInf.RmtInf N Remittance info. If present, only one of the following element should be set:
  • "Ustrd" - Unstructured Remittance Info. Max 140Chars, but some banks allows shorter.
  • "Strd" - Structured Remittance Info, E.g. KID in Norway.

Pain validation and errors lists

If pain content validation fails, 400 error are returned with type=urn:pain-invalid with a list of validations errors with path to exactly the error is.

An example of a pain file with a parse error, Empty ID tag. The parser may report more than one error related to an item (same line and column number).

{
  "type" : "urn:pain-invalid",
  "title" : "Invalid Pain",
  "detail" : "There are 2 errors",
  "detail2" : "",
  "errors" : [ {
    "type" : "parse",
    "errors" : [ {
      "message" : "cvc-minLength-valid: Value '' with length = '0' is not facet-valid with respect to minLength '1' for type 'Max35Text'.",
      "line" : 21,
      "column" : 38
    }, {
      "message" : "cvc-type.3.1.3: The value '' of element 'Id' is not valid.",
      "line" : 21,
      "column" : 38
    } ]
  } ],
  "traceId" : "803ec5f6-3382-4230-88c0-fa19e4499720"
}
An example of a pain file with a transaction error, Invalid IBAN.

{
  "type" : "urn:pain-invalid",
  "title" : "Invalid Pain",
  "detail" : "There are 1 errors",
  "detail2" : "",
  "errors" : [ {
    "type" : "transaction",
    "endToEndId" : "197102754228134604603270233998",
    "errors" : [ {
      "message" : "IBAN failed checksum validation",
      "path" : "cstmrCdtTrfInitn.paymentInformation[0].creditTransferTx[0].cdtrAcct.id.iban"
    } ]
  } ],
  "traceId" : "b3515c0f-a474-470c-a58f-0d2478f3268f"
}
An example of pain xml with transaction error with missing mandatory elements.
{
  "type": "urn:pain-invalid",
  "title": "Invalid Pain",
  "detail": "There are 2 errors",
  "detail2": "",
  "errors": [
    {
      "type": "transaction",
      "endToEndId": "966700472476787235510074468580",
      "errors": [
        {
          "code": "urn:creditor-name-missing",
          "message": "Creditor name is required",
          "path": "cstmrCdtTrfInitn.paymentInformation[0].creditTransferTx[0].creditor.name"
        }
      ]
    }
  ],
  "traceId": "03f55df4-d7d7-416a-add6-0f427eec7f92"
}
An example of a pain file with a general error, Sum mismatch.
{
  "type" : "urn:pain-invalid",
  "title" : "Invalid Pain",
  "detail" : "There are 1 errors",
  "detail2" : "",
  "errors" : [ {
    "type" : "general",
    "errors" : [
    {
      "code": "urn:sum-mismatch",
      "message" : "Control sum and total sum of transactions mismatch. Expected total amount of 100 but was 12.00",
      "path" : "cstmrCdtTrfInitn"
    } ]
  } ],
  "traceId" : "4fdce039-f9f1-4fc2-b91a-88580bc3bbb8"
}

International payments

Additional requirement in pain xml for international payments are:

An example of a remittance info with payment reason and purpose code (INR/India specific)
<RmtInf>
  <Ustrd>Payment reason</Ustrd> <!-- Payment reason described in free text, max 35 characters -->
  <Ustrd>P0802</Ustrd> <!-- Payment purpose code, note that the Miscellaneous Purpose Code (P1099) is not permitted -->
</RmtInf>

Status (Pre SCA)

Status Pre SCA

The initiate payment response contains a statusUrl. This Url is used in the remaining payment steps, but the response and statuses will differ depending on the payment status.

Payment status right the transaction has been created
curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/payments/c766e7e0-ea15-4b4a-9574-a27c763f6c17/status \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Example response:
{
  "flowId": "c766e7e0-ea15-4b4a-9574-a27c763f6c17",
  "processStatus": "active",
  "messageId": "1233-12211"
}

The payment transaction goes through a process, and is active as long as processStatus = active. The response will change as the transaction process goes through its different stages.

Status with total cost related to currency exchange.
{
  "scaUrl" : "https://***",
  "processStatus" : "active",
  "scaStatus" : "AuthorizationRequired",
  "transactions" : [ {
    "status" : "AuthorizationRequired",
    "statusReason" : "Payment accepted at Aspsp and needs to be signed by PSU. Please initiate payment signing process",
    "purposeCode" : "OTHR",
    "commission" : {
      "amount" : "0.25",
      "currency" : "NOK"
    },
    "transactionAmount" : {
      "amount" : "125.78",
      "currency" : "NOK"
    },
    "exchangeRate" : "10.4818",
    "displayExchangeRate" : "10.4818",
    "exchangeRateExpiration" : "2022-08-05T14:48:14.133+02:00",
    "fromCurrency" : "NOK",
    "toCurrency" : "EUR",
    "dueDate" : "2022-02-07"
  } ]
}

International transactions contains useful information like exchangeRate, displayExchangeRate, transactionAmount, commission and exchangeRateExpiration. It's recommended to show the displayExchangeRate and transactionAmount to the customer before SCA signing.

Signing (SCA) international transactions includes a currency exchange commitment. It's important to notice that there is a cancellation cost for the customer if the transaction are cancelled or not completed due to insufficient fund after the transaction has been signed. We recommend that either of the following messages should be displayed and confirmed by the user prior to signing international transactions:

SCA

SCA

Normally the payment is ready to be signed when the status response contains scaUrl and scaStatus AuthorizationRequired.

Regular SCA flow:
{
   "scaStatus": "AuthorizationRequired",
   "scaUrl": "[sca-url]"
}

At this point, the scaUrl should be presented to the user. After the SCA has been successfully completed, the customer will be redirected to the scaRedirectUrl specified in the payment initiation request.

You can then get the status of the payment after the redirect to check the SCA status.

Note: The SCA link that the customer is redirected to initially should also be available in ERP interface on the payments line. That way the customer can click and re-enter the SCA flow to complete the signing. If the customer doesn't do this the flow will automatically expire after some time.

Note that there are some cases in which the payment does not need signing (this is bank specific and can depend on transaction amount, receiver's bank, etc). In such cases no scaUrl or scaStatus will be provided, and the transaction goes to "AcceptedSettlementInProgress" status. Thus, the absence of the scaUrl in "get payment information" response does not necessarily mean that the payment initiation request has failed, here checking the 'processStatus' field (should be "active") can be used to determine if the initiation was successful.

Process flow and dealing with aborted SCA

Accepted SCA:
{
  "flowId": "6613c5c0-642f-4ae7-98ee-a8ccdaa486bb",
  "processStatus": "active",
  "scaStatus": "Accepted",
  "transactions": [
    {
      "endToEndID": "296edf186a374b0a89edfa8e9fd054c5",
      "status": "AcceptedSettlementInProgress",
      "transactionAmount": {
        "amount": "1235",
        "currency": "NOK"
      },
      "commission": {
        "amount": "0",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "NOK",
      "exchangeRate": 1,
      "paymentId": "0fsdfasdfsa3e4d46218b80179d5c19f796"
    },
    {
      "endToEndID": "wd459dp10b7plasjdl202aas5",
      "status": "Rejected",
      "transactionAmount": {
        "amount": "1234.14",
        "currency": "NOK"
      },
      "commission": {
        "amount": "14.14",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "SEK",
      "exchangeRate": 1.2345
    },
    {
      "endToEndID": "iojadoiwajdijoa123",
      "status": "Booked",
      "transactionAmount": {
        "amount": "1235",
        "currency": "NOK"
      },
      "commission": {
        "amount": "0",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "NOK",
      "exchangeRate": 1,
      "paymentId": "0fsdfasfaserairpasr3484543p5wvretv"
    }
  ],
  "messageId": "1233-12211"
}

When the user is sent back to the partner system after completion (or cancelling) of SCA or just coming back after closing the browser window the system should make an API call to get an updated payment status. The SCA URL can be retried as long as the status is AuthorizationRequired. If the status is Rejected or Cancelled, the transaction has failed. In case where the status is AcceptedSettlementInProgress and AcceptedSettlementCompleted the system should just wait for the status Booked to be sure that the process is completed.

When SCA are completed, the scaStatus are changed to "Accepted"

If the user cancels the SCA signing, the scaStatus will be displayed as Rejected, and transactions status AuthorizationRequired (since the payment can still be signed from an online bank).

A transaction, where user cancels the SCA:
{
  "flowId": "117b7ce7-a133-4dc6-a8d9-d964d18c8582",
  "processStatus": "completed",
  "scaStatus": "Rejected",
  "transactions": [
    {
      "endToEndID": "296edf186a374b0a89edfa8e9fd054c5",
      "status": "Cancelled",
      "transactionAmount": {
        "amount": "1235",
        "currency": "NOK"
      },
      "commission": {
        "amount": "0",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "NOK",
      "exchangeRate": 1,
      "paymentId": "434wrw43wairpasr3484543p5wvretv"
    },
    {
      "endToEndID": "wd459dp10b7plasjdl202aas5",
      "status": "Cancelled",
      "transactionAmount": {
        "amount": "1234.14",
        "currency": "NOK"
      },
      "commission": {
        "amount": "14.14",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "SEK",
      "exchangeRate": 1.2345,
      "paymentId": "2424lwl4iwl34w34484543p5wvretv"
    },
    {
      "endToEndID": "iojadoiwajdijoa123",
      "status": "Cancelled",
      "transactionAmount": {
        "amount": "1235",
        "currency": "NOK"
      },
      "commission": {
        "amount": "0",
        "currency": "NOK"
      },
      "fromCurrency": "NOK",
      "toCurrency": "NOK",
      "exchangeRate": 1,
      "paymentId": "2424iwl34w34484543p5wvretv"
    }
  ],
  "messageId": "1233-12211"
}

In order to avoid duplicate payments and confusion for the end users, all unsigned transactions will be automatically cancelled after 25 minutes from payment initiation. The the scaStatus will be set to Timeout, and transaction status to Cancelled with status reason Payment was cancelled due to SCA timeout. The scaUrl will be set to null.

Note that not all banks support automatic cancellation, in such cases the transactions will stay unchanged and will be available for signing even after 25 minutes.

Transaction with SCA timeout
{
  "scaUrl": null,
  "flowId": "cc408c13-28eb-4730-8b75-006f239e019e",
  "processStatus": "completed",
  "scaStatus": "Timeout",
  "error": null,
  "messageId": "25816052-88A4-49CE-994B-4EF149FE1C5F",
  "transactions": [
    {
      "endToEndID": "553253946837740742720797270914",
      "status": "Cancelled",
      "purposeCode": "OTHR",
      "statusReason": "Payment was cancelled due to SCA timeout",
      "exchangeRate": 1,
      "fromCurrency": "NOK",
      "toCurrency": "NOK",
      "dueDate": "2022-09-05",
      "paymentId": "4ff415b2901e4737a2f262e2fa356037",
      "errors": "null"
    }
  ]
}

Notice that the processStatus is set to completed when the SCA signing fails.

Payment Status

Status

The transaction is added to the process after a successful SCA. Depending on the due date of the transaction, it might take several days before it will be completed. The transaction status will be updated during the process. The table below shows an overview of the possible statuses.

Status Description
Imported ZTL has imported and parsed the transaction, but no further action has been taken.
Initialized ZTL has initialized the transaction, but the status from the bank is still unknown.
AuthorizationRequired The user needs to complete SCA and the URL for that is in the API response.
Rejected The bank rejected the payment.
Cancelled The payment has been cancelled or SCA has timed out.
AcceptedSettlementInProgress The bank has accepted the transaction, and the payment is ongoing.
AcceptedSettlementCompleted The payment has been completed by the bank.
Booked The payment is successful and process completed at ZTL.
RejectedFundsNotReceivedInTime ZTL has rejected the payment due to not having received the funds by the due date. This is often due to insufficient funds on the debtor account. Notice that unless the payment is also cancelled in/by the bank, the initial payment may still be processed and paid to ZTL at a later time. In this scenario, ZTL will not forward the funds to the creditor. Instead ZTL will return the funds to the debtor
HeldByBank Transaction is temporary stopped by the bank. Customer should contact bank. This status is only shown for partners who have enabled this feature.
UnknownStateInProgress Transaction is in an unknown state (last known status was Rejected) and can be assumed to be in progress until status is updated. The status is a temporary status used for Evry banks.

Errors

The status response will contain an error property if an error occurs during the transaction process. The errors can occur for both the payment job/basket, and for the individual transactions.

{
   "flowId" : "56563af2-a199-44ec-92a1-d77eed2d8562",
   "processStatus" : "completed",
   "error" : {
      "type" : "urn:quote-failed",
      "title" : "Payment rejected",
      "detail" : null,
      "instance" : "/api/payments/56563af2-a199-44ec-92a1-d77eed2d8562/status"
   },
   "messageId" : "8A993B4B-8FE8-43AD-924C-DFFEB1D4A430",
   "transactions" : [ {
      "endToEndID" : "381973911341275448394637392141",
      "status" : "Imported",
      "purposeCode" : "OTHR",
      "errors" : [ {
         "type" : "urn:payment:invalid-due-date",
         "detail" : "Due date can be maximum 8 business days ahead"
      } ],
      "fromCurrency" : "NOK",
      "toCurrency" : "EUR",
      "dueDate" : "2022-02-18"
   } ]
}

PaymentJob Errors

Type Description
urn:quote-failed May occur for international payments, if there are problems receiving quote for at least one transaction
urn:company-disabled The company making the payment is disabled (i.e. is not allowed to make payments)
urn:input-validation-failed The payment(s) fails validation (e.g account number, KID, address)
urn:internal-server-error May occur for different reasons when there is a technical problem with the payment solution
urn:unknown-error May occur for different reasons, such as when there is a problem with an external service
urn:bank-downtime The bank service is down
urn:exchange-provider-downtime The currency exchange provider is down
urn:ocr-error The OCR (KID in Norway) rules for the creditor account are violated, e.g. missing KID, creditor account doesn't have an OCR agreement or a KID number that doesn't match the creditor account
urn:payments-rejected May occur for different reasons when the bank rejects one of the payments during payment initiation requests
urn:consent-expired Consent has expired

Possible Transaction Errors

Type Description
urn:payment:invalid-due-date May occur with payments with invalid due-date
urn:payment:max-amount-exceeded-for-currency May occur if amount exceeds max amount for currency/date. There are limitations for max amount for forward transactions
urn:payment:currency-invalid-due-date Provided currency does not support provided due date
urn:payment:international-payment-service-closed International payment service is closed during weekend

Settled

Settled

The transaction has been successfully completed once the status shows that it's booked:

{
  "flowId": "117b7ce7-a133-4dc6-a8d9-d964d18c8582",
  "processStatus": "completed",
  "transactions": [
    {
      "status": "Booked"
    }
  ],
  "messageId": "1233-12211"
}

Cancel

curl -X POST https://api.sandbox.ztlpay-test.io/api/payments/cancel?consentReference=b0fc2430-3d87-470d-83f5-a4e94d540f6f \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 49ae0cfe-6b72-4310-81f5-ad4eef897fe3' \
  -H 'PSU-IP-Address: 153.110.241.229' \
  -H 'Authorization: Bearer ACCESS_TOKEN'
  -d '{
    "instructionId" : "testydsddff345",
    "callbackUrl" : "https://www.example.com",
    "paymentId" : "c04de502623d4e6f9e8b632278342859"
  }'
201 Response
{
    "statusReason": {
        "reason": "Payment cancellation request is initiated. PSU needs to confirm the cancellation",
        "status": "AuthorizationRequired",
        "origin": "DNB at NAAS"
    },
    "scaUrl": "https://lldkit7c6f.execute-api.eu-west-1.amazonaws.com/Prod/bankid/logon?blob=eyJ1c2VySWQiOiIzMTEyNTQ2MTExOCIsInJlZGlyZWN0VXJsIjoiaHR0cHM6Ly9zYW5kYm94Lm5hYXMtdGVzdDEubmV0cy5ldS9kbmJhbm9ray9jYW5waXMvYWNjZXB0L2MwNGRlNTAyNjIzZDRlNmY5ZThiNjMyMjc4MzQyODU5IiwibW9kZSI6IlByb2QiLCJjb25zZW50SWQiOiJlMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJhY2Nlc3NVcmwiOiJhcHBzL25icC9iZXRhbGluZyIsInBzZDJFbmRwb2ludCI6Imh0dHBzOi8vYXBpLmludGVybmFsMDEuc2FuZGJveC5hcHBzaGFyZWRzdmMudGVjaC0wMy5uZXQvdjEvbmV0YmFuay9wcm94eS9wYXltZW50L3NjYS9lMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJjb25zZW50RW5kcG9pbnQiOiJodHRwczovL2FwaS5pbnRlcm5hbDAxLnNhbmRib3guYXBwc2hhcmVkc3ZjLnRlY2gtMDMubmV0L3YxL25ldGJhbmsvcHJveHkvYXV0aG9yaXNhdGlvbi9lMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJwYXltZW50SWQiOiJTU0xfRE9NLTQ5MTQzMjk2MyIsInRwcCI6IlBTRE5PLVRTVE5DQS03ZmEwNjZiMXxOZXRzIFRQUCIsInRpbWVzdGFtcCI6IjE2MDM3ODQyNTYyMTcifQ==&redirectUrl=https%3A%2F%2Fsandbox.naas-test1.nets.eu%2Fdnbanokk%2Fcanpis%2Fdeny%2Fc04de502623d4e6f9e8b632278342859&la"
}
400 Response
{
    "type": "urn:cancellation-not-supported-on-payment-date",
    "title": "Cancellation not supported",
    "detail": "Bank with bic 'NDEANOKK' does not support cancellation on the payment execution date"
}

Cancel

Payments can be cancelled as long as they have not reached booked status. For cancellation, the PSD2 paymentId are used, obtained from the status endpoint.

Before the user signs the cancellation of an international transaction, either of the following messages should be displayed and confirmed by the user:

These messages must remain unaltered. If a translation is missing, please reach out to us.

Note: Nordea does not support cancelling payments on the same day as execution date, a 400 response with an error of type "urn:cancellation-not-supported-on-payment-date" will be sent in such cases.

Payroll

Initiating a payroll payment

curl -X POST \
  https://api.sandbox.ztlpay-test.io/api/payroll \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  -H 'PSU-IP-Address: 192.158.1.38' \
  -d '{
        "base64Pain001": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiP...mZJbml0bj4NCjwvRG9jdW1lbnQ+DQo=",
        "legalId": "NO89453784",
        "consentReference": "8eerj3e-ds79jdc",
        "callbackUrl": "http://",
      }'
// Example successful payroll payment initiation response
{
  "payrollId": "c766e7e0-ea15-4b4a-9574-a27c763f6c17",
  "sca": "https://www.dnb.no/segb/appo/logon/psd2Start?blob=P98u8VFpnug..."
}

Headers description

Body description

Requirements for PAIN file

Please note that the deadline to initiate payroll payment and complete SCA is set to 11:00:00 CEST the same day as the desired execution date. However, we recommend to initiate the payroll the day before in case of possible bank downtimes and delays to make sure the recipients get their salaries in time. For example, if a payroll payment needs to be completed on the 23 May 2022, the SCA for payroll initiation must be completed before 11:00:00 on May 23rd 2022. (Best practice would be to do this the day before execution date, May 22nd).

PAIN file goes through several rounds of validation to make sure that a valid payroll payment request can be placed. Response code 422 and a list of validation errors are returned in case the PAIN file fails validation. Most of the validation errors are assigned a distinct code for easier error handling and better end-user experience, following table describes possible error codes:

Code Description
urn:number-of-transactions-invalid Provided total number of transactions in a payment job does not match actual number of transactions
urn:sum-mismatch Provided control sum of transaction amounts in a payment job does not match actual sum of transaction amounts
urn:end-to-end-id-not-unique One or several transaction endToEndIDs are used more than one time in the same payment job
urn:[creditor/debtor]-country-missing Country code is missing from creditor/debtor (specified in 'path' field) postal address
urn:max-length-exceeded The value in the field provided under 'path' in error object has exceeded maximum length
urn:amount-invalid Transaction amount decimal scale exceeds the limit for given currency
urn:[creditor/debtor]-name-missing Name is missing from creditor/debtor (specified in 'path' field) account information
urn:bic-unknown Provided BIC in creditor/debtor (specified in 'path' field) account information could not be matched to a bank or
BIC could not be derived from provided account number
urn:invalid-characters The value in the field provided under 'path' in error object contains invalid characters
urn:due-date-invalid Provided requested execution date is in the past or is a non-banking day
urn:bban-invalid Provided creditor/debtor (specified in 'path' field) BBAN is invalid
urn:iban-or-bban-missing Provided creditor/debtor (specified in 'path' field) account information is missing account number
urn:organization-account-mismatch Provided organization ID does not own the debtor account
urn:currency-invalid Provided currency is invalid for a payroll payment (currently only NOK is supported)
urn:debtor-post-code-invalid Debtor address is set to Norway, but the given postal code is not a valid Norwegian postal code
urn:creditor-post-code-invalid Creditor address is set to Norway, but the given postal code is not a valid Norwegian postal code
// Example failed payroll payment initiation response
{
  "status": "422",
  "title": "Failed to process payroll payment request",
  "errors": [
    {
      "code": "urn:due-date-invalid",
      "path": "CstmrCdtTrfInitn.PmtInf.ReqdExctnDt",
      "message": "Requested execution date is invalid, must be a business day and not in the past",
      "developerText": "Requested execution date 12-03-2010 is invalid, must be a business day and not in the past"
    }
  ]
}
<!-- Example Pain Xml for a payroll payment -->
<?xml version="1.0" encoding="utf-8"?>
<Document xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.03">
  <CstmrCdtTrfInitn>
    <GrpHdr>
      <MsgId>5057ed457e16419188f92c8f7141350a</MsgId>
      <CreDtTm>2021-08-28T17:31:12.2039778</CreDtTm>
      <NbOfTxs>3</NbOfTxs>
      <CtrlSum>83527.3</CtrlSum>
      <InitgPty>
        <Nm>Ztl Payment Solution AS</Nm>
        <PstlAdr>
          <Ctry>NO</Ctry>
        </PstlAdr>
        <Id>
          <OrgId>
            <Othr>
              <Id>920970931</Id>
              <SchmeNm>
                <Cd>CUST</Cd>
              </SchmeNm>
            </Othr>
          </OrgId>
        </Id>
        <CtryOfRes>NO</CtryOfRes>
      </InitgPty>
    </GrpHdr>
    <PmtInf>
      <PmtInfId>1tCi5w6IS0W1B4UPaaCvA</PmtInfId>
      <PmtMtd>TRF</PmtMtd>
      <BtchBookg>true</BtchBookg>
      <CtrlSum>83527.30</CtrlSum>
      <PmtTpInf>
        <SvcLvl>
          <Cd>NURG</Cd>
        </SvcLvl>
        <LclInstrm>
          <Prtry>DO</Prtry>
        </LclInstrm>
        <CtgyPurp>
          <Cd>SALA</Cd>
        </CtgyPurp>
      </PmtTpInf>
      <ReqdExctnDt>2021-08-31</ReqdExctnDt>
      <Dbtr>
        <Nm>Some company AS</Nm>
        <PstlAdr>
          <Ctry>NO</Ctry>
        </PstlAdr>
        <Id>
          <OrgId>
            <Othr>
              <Id>563909378</Id>
            </Othr>
          </OrgId>
        </Id>
        <CtryOfRes>NO</CtryOfRes>
      </Dbtr>
      <DbtrAcct>
        <Id>
          <Othr>
            <Id>47011155568</Id>
            <SchmeNm>
              <Cd>BBAN</Cd>
            </SchmeNm>
          </Othr>
        </Id>
        <Ccy>NOK</Ccy>
      </DbtrAcct>
      <DbtrAgt>
        <FinInstnId>
          <BIC>SNOWNO22</BIC>
        </FinInstnId>
      </DbtrAgt>
      <ChrgBr>SHAR</ChrgBr>
      <CdtTrfTxInf>
        <PmtId>
          <InstrId>mfQQLEc1EEeYufjjqlpkGw-1</InstrId>
          <EndToEndId>mfQQLEc1EEeYufjjqlpkGw-1</EndToEndId>
        </PmtId>
        <Amt>
          <InstdAmt Ccy="NOK">15061.38</InstdAmt>
        </Amt>
        <Cdtr>
          <Nm>Leonard James</Nm>
          <PstlAdr>
            <StrtNm>Leonardgaten 3</StrtNm>
            <PstCd>8001</PstCd>
            <TwnNm>BODØ</TwnNm>
            <Ctry>NO</Ctry>
            <AdrLine>Leonardgaten 3</AdrLine>
            <AdrLine>8001 BODØ</AdrLine>
          </PstlAdr>
          <CtryOfRes>NO</CtryOfRes>
        </Cdtr>
        <CdtrAcct>
          <Id>
            <Othr>
              <Id>31624847906</Id>
              <SchmeNm>
                <Cd>BBAN</Cd>
              </SchmeNm>
            </Othr>
          </Id>
        </CdtrAcct>
        <RmtInf>
          <Ustrd>Lønn fra Ztl Payment Solution AS</Ustrd>
        </RmtInf>
      </CdtTrfTxInf>
      <CdtTrfTxInf>
        <PmtId>
          <InstrId>mfQQLEc1EEeYufjjqlpkGw-2</InstrId>
          <EndToEndId>mfQQLEc1EEeYufjjqlpkGw-2</EndToEndId>
        </PmtId>
        <Amt>
          <InstdAmt Ccy="NOK">23138.17</InstdAmt>
        </Amt>
        <Cdtr>
          <Nm>Diana Powell</Nm>
          <PstlAdr>
            <StrtNm>Dianagaten 2</StrtNm>
            <PstCd>4985</PstCd>
            <TwnNm>VEGÅRSHEI</TwnNm>
            <Ctry>NO</Ctry>
            <AdrLine>Dianagaten 2</AdrLine>
            <AdrLine>4985 VEGÅRSHEI</AdrLine>
          </PstlAdr>
          <CtryOfRes>NO</CtryOfRes>
        </Cdtr>
        <CdtrAcct>
          <Id>
            <Othr>
              <Id>19499114608</Id>
              <SchmeNm>
                <Cd>BBAN</Cd>
              </SchmeNm>
            </Othr>
          </Id>
        </CdtrAcct>
        <RmtInf>
          <Ustrd>Lønn fra Ztl Payment Solution AS</Ustrd>
        </RmtInf>
      </CdtTrfTxInf>
      <CdtTrfTxInf>
        <PmtId>
          <InstrId>mfQQLEc1EEeYufjjqlpkGw-3</InstrId>
          <EndToEndId>mfQQLEc1EEeYufjjqlpkGw-3</EndToEndId>
        </PmtId>
        <Amt>
          <InstdAmt Ccy="NOK">45327.75</InstdAmt>
        </Amt>
        <Cdtr>
          <Nm>Jason Morgan</Nm>
          <PstlAdr>
            <StrtNm>Jasongaten 4</StrtNm>
            <PstCd>3544</PstCd>
            <TwnNm>TUNHOVD</TwnNm>
            <Ctry>NO</Ctry>
            <AdrLine>Jasongaten 4</AdrLine>
            <AdrLine>3544 TUNHOVD</AdrLine>
          </PstlAdr>
          <CtryOfRes>NO</CtryOfRes>
        </Cdtr>
        <CdtrAcct>
          <Id>
            <Othr>
              <Id>40846824008</Id>
              <SchmeNm>
                <Cd>BBAN</Cd>
              </SchmeNm>
            </Othr>
          </Id>
        </CdtrAcct>
        <RmtInf>
          <Ustrd>Lønn fra Ztl Payment Solution AS</Ustrd>
        </RmtInf>
      </CdtTrfTxInf>
    </PmtInf>
  </CstmrCdtTrfInitn>
</Document>

Status

curl -X GET \
  https://api.sandbox.ztlpay-test.io/api/payroll/status/c766e7e0-ea15-4b4a-9574-a27c763f6c17 \
  -H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
// Example response
{
  "statusName": "SETTLEMENT_IN_PROGRESS",
  "statusReason": "Initiated payment has been accepted for execution",
  "timestamp": "2021-11-30T10:54:34.489808485+01:00"
}

payrollId the unique payroll ID provided in the response when initiating a payroll payment

Possible statuses are:

Status Description
AWAITING_AUTHORIZATION Awaiting authorization of funding payment to ZTL's client account
SETTLEMENT_IN_PROGRESS Funding payment to ZTL's client account initiated
SETTLEMENT_COMPLETED Funding payment to ZTL's client account completed
CANCELLED Funding payment to ZTL's client account was cancelled
SETTLEMENT_NOT_COMPLETED_IN_TIME Funding payment to ZTL's client account has not been completed in reasonable time for completing payroll payment execution on the requested execution date, the payroll payment will not go through and money will be transferred back to customer
PAYROLL_PAYMENT_INITIATED Payout payment has been sent to ZTL's payroll payment provider
PAYROLL_PAYMENT_ACCEPTED_TECHNICAL_VALIDATION Payout payment has passed technical validation at ZTL's payroll payment provider
ACCEPTED_FOR_EXECUTION Payout payment has been accepted for execution at ZTL's payroll payment provider
EXECUTION_COMPLETED Payroll payment has been paid out to the recipients
FAILED Payout payment failed at ZTL's payroll processor

Cancel

It is possible to cancel a payroll payment with statuses AWAITING_AUTHORIZATION, SETTLEMENT_IN_PROGRESS and SETTLEMENT_NOT_COMPLETED_IN_TIME as long as status of the funding payment in the bank allows it.

curl -X POST \
  https://api.sandbox.ztlpay-test.io/api/payroll/cancel \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 49ae0cfe-6b72-4310-81f5-ad4eef897fe3' \
  -H 'PSU-IP-Address: 153.110.241.229' \
  -H 'PSU-Geo-Location: string' \
  -H 'PSU-User-Agent: string' \
  -H 'Authorization: Bearer ACCESS_TOKEN'
  -d '{
        "payrollId" : "1887c36b-4be7-4574-8954-9229c9666631",
        "instructionId" : "testydsddff345",
        "consentReference": "8eerj3e-ds79jdc",
        "callbackUrl": "https://www.example.com"
      }'
// Example response
{
    "statusReason": {
        "reason": "Payment cancellation request is initiated. PSU needs to confirm the cancellation",
        "status": "AuthorizationRequired",
        "origin": "DNB at NAAS"
    },
    "scaUrl": "https://lldkit7c6f.execute-api.eu-west-1.amazonaws.com/Prod/bankid/logon?blob=eyJ1c2VySWQiOiIzMTEyNTQ2MTExOCIsInJlZGlyZWN0VXJsIjoiaHR0cHM6Ly9zYW5kYm94Lm5hYXMtdGVzdDEubmV0cy5ldS9kbmJhbm9ray9jYW5waXMvYWNjZXB0L2MwNGRlNTAyNjIzZDRlNmY5ZThiNjMyMjc4MzQyODU5IiwibW9kZSI6IlByb2QiLCJjb25zZW50SWQiOiJlMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJhY2Nlc3NVcmwiOiJhcHBzL25icC9iZXRhbGluZyIsInBzZDJFbmRwb2ludCI6Imh0dHBzOi8vYXBpLmludGVybmFsMDEuc2FuZGJveC5hcHBzaGFyZWRzdmMudGVjaC0wMy5uZXQvdjEvbmV0YmFuay9wcm94eS9wYXltZW50L3NjYS9lMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJjb25zZW50RW5kcG9pbnQiOiJodHRwczovL2FwaS5pbnRlcm5hbDAxLnNhbmRib3guYXBwc2hhcmVkc3ZjLnRlY2gtMDMubmV0L3YxL25ldGJhbmsvcHJveHkvYXV0aG9yaXNhdGlvbi9lMTdkYmMzMy1mNDM5LTQ0NmYtOWY3YS1jYTc0ZjcyNjExYzUiLCJwYXltZW50SWQiOiJTU0xfRE9NLTQ5MTQzMjk2MyIsInRwcCI6IlBTRE5PLVRTVE5DQS03ZmEwNjZiMXxOZXRzIFRQUCIsInRpbWVzdGFtcCI6IjE2MDM3ODQyNTYyMTcifQ==&redirectUrl=https%3A%2F%2Fsandbox.naas-test1.nets.eu%2Fdnbanokk%2Fcanpis%2Fdeny%2Fc04de502623d4e6f9e8b632278342859&la"
}

Breaking Changes

Upcoming Changes

There are no planned breaking changes :)

Although we have no major breaking changes planned, we're continuously improving our systems. If you have any feature requests, no matter the size, feel free to contact us!

Previous changes

2022-05-06 - Enforce whitelisting of banks

Starting 2022-05-06, only banks retrieved from the supported banks-service may be used with our services.

Previously, there has been a separation between those banks it has been possible to use our services with, and those banks that we have rigorously tested and confirmed to work as expected with our services. Allowing our services to be used with banks that we have not verified, has caused confusion and unwanted behavior. We will therefore, starting 2022-05-06, block requests towards banks that we do not explicitly support.

If there are any banks that you currently use our services with, that are not in the supported banks-service, we ask that you let us know what bank and which services.

Starting 2022-05-02, the header field PSU-IP-Address will be mandatory when creating a new consent-token.

This change will enable a more seamless experience when creating consents across a variety of banks, as some banks will not allow consent creation without the header field. This change will not affect any existing consents.

To prepare for this change, users of the API may begin providing the header field in all requests for creating consent at earliest convenience.

2022-03-08 - Validate length of unstructured remittance info

Starting 2022-03-08, the length of the PAIN-field CdtTrfTxInf.RmtInf.Ustrd will be validated based on the bank-specific field constraints.maxUnstructuredRemittanceInfoLength, which can be retrieved from the supported banks-service.