Tutorial
How the Wero Merchant Payment API Works
- Request an access token using OAuth 2.0 client credentials. The authentication inputs depend on whether you access the Sandbox or Production environment.
Note: Please include the required scopes for the endpoints you wish to access in your authorization request. They are detailed in the OAuth 2.0 Scopes.
- Call Create Wero Payment to create a Wero payment and use the redirect url in the response to direct the customer to Wero e-commerce flow.
- Consumer with the help of their Issuing bank grants the consent to the Wero payment. Issuing bank directs consumer to the merchant.
- Retrieve the payment status by using Get Wero Payment Status endpoint.
Requirements
To use this API in a production environment as an ABN AMRO client, you must have the following:
- A contract for Wero Direct merchant or Wero Payment facilitator
- A completed OAuth setup. For detailed steps, refer to the Production access.
Sandbox access
The sandbox and production environments are functionally identical. The sandbox is static, which means that you can perform all operations without making any payments on an account. Payments posted in the sandbox environment are cleaned daily.
To use the API in the sandbox environment, complete the following steps:
- Register and create an account:
- Click Sign up.
- Enter your details, and click Create an account.
- Developer Support will send you an activation link by email.
- Click the activation link.
- Create and register an application:
- Log in to your account.
- In the left-side navigation bar, click Apps.
- Click Add app.
- In the App name field, enter a name for your application.
- Select Wero Merchant Payment from the APIs list, and click Add app.
- Complete the How to use this API.
Sandbox access details
- Sandbox authorization URL: https://auth-sandbox.abnamro.com/as/token.oauth2
| Attribute | Value |
|---|---|
| client_id | wero_merchant2 |
| API-Key | The API Key for your application on the Developer Portal |
Production access
To get access to production for ABN AMRO clients:
- Log in to your account.
- In the left-side navigation bar, click Apps.
- Click Request Production Access.
- Select the API category that you want to request production access to.
Note: It is not possible to request production access for multiple API categories in one request.
- Fill in the form, and click Submit.
- You receive a confirmation email and ticket-Id.
- ABN AMRO Developer Support validates the form and subsequently reaches out to you to collect your public key details required for private_key_jwt authentication. Refer Authentication.
- When the setup is complete, ABN AMRO Developer Support contacts you and supplies you with a client_id.
- A new app is added in Apps. This new app contains your API key.
For production access, contact us.
Production access details
- Production Token URL: https://auth.abnamro.com/as/token.oauth2
| Attribute | Value for Production |
|---|---|
| client_id | As supplied to you by ABN AMRO |
| API-Key | The API Key for your production application on the Developer Portal |
Authentication
API consumers can authenticate themselves with ABN AMRO through OAuth 2.0 by using the jwt-bearer grant as specified by by RFC 7523.
The consumer creates a signed JWT, commonly called a client assertion, using a private key. The corresponding public key is registered with our OAuth platform with a unique kid.
Note: The private key must always remain under the control of the consumer and must never be shared with us.
Key Generation Options
- Software-generated keys, for example using OpenSSL.
- Hardware-protected keys, using an HSM or equivalent cryptographic key-management service. The private key is generated and securely stored within the hardware device and is never exported. All signing operations are performed within the secure device using the private key. The corresponding public key can be exported from the device and shared with ABN AMRO for registration.
Example of generating a key pair using OpenSSL
Following are OpenSSL commands to generate a 3072-bit RSA key pair:
Generate the RSA private key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out private_key.pem
Extract the corresponding public key
openssl pkey -in private_key.pem -pubout -out public_key.pem
This creates:
- private_key.pem - RSA private key. Keep this key secure and never share it.
- public_key.pem - RSA public key. The public key is shared with ABN AMRO Developer Support upon request.
Request Access Token
The process for obtaining an access token from ABN AMRO includes the following steps:
- Create and sign a JWT assertion:
- Generate an assertion token in the form of a JSON Web Token (JWT).
- Sign the JWT asymmetrically using a valid private key or certificate.
- Making an endpoint call to obtain an access token from ABN AMRO OAuth services and including the following in the call:
- The assertion token
- The scope of the actions that represent the requested access rights
Assertion token
Assertion tokens consist of the following parts, which are separated by a dot:
BASE64URL(header) . BASE64URL(payload) . BASE64URL(signature)
Header
{
"alg": "RS256",
"kid": "2c9b3e1a-8001-4c6f-95ae-2959d38d0000",
"typ": "JWT"
}
| Path | Type | Description |
|---|---|---|
| alg | String | Identifies the cryptographic algorithm used to sign the assertion token. Permitted values are: RS256, RS384, RS512, ES256, ES384, ES512. |
| kid | String | Key ID. This is the unique identifier of the key used to sign the assertion token. The value of this field must match the kid value of the public key registered with ABN AMRO. |
| typ | String | (Optional) Type. Must be set to "JWT" if included. |
Payload
{
"jti": "99325f42-1c85-4427-b081-81fbeeeb1c91",
"iss": "wero_merchant2",
"sub": "wero_merchant2",
"aud": "https://auth-sandbox.abnamro.com",
"scope": "merchant:wero-payment:all",
"iat": 1776757599,
"exp": 1776758199
}
| Path | Type | Description |
|---|---|---|
| jti | String | JWT ID. A unique identifier for the assertion token. This value is used to prevent replay attacks. |
| iss | String | Issuer. This is the unique identifier of the client that is requesting the access token. The value of this field must match the client_id value registered with ABN AMRO. |
| sub | String | Subject. This field is typically used to identify the principal that is the subject of the JWT. this is same as the iss field. |
| aud | String | Audience. This field identifies the recipients that the JWT is intended for. For ABN AMRO's authorization server, this value must be set to the token endpoint URL Production: https://auth.abnamro.com/as/token.oauth2Sandbox: https://auth-sandbox.abnamro.com/as/token.oauth2 |
| scope | String | List of identifiers that denote the scope of the actions for which the client requests authorization. Multiple scopes are delimited by blanks |
| iat | NumericDate | Issued At. This is the time at which the assertion token was issued. The value must be a Unix timestamp (number of seconds since January 1, 1970). |
| exp | NumericDate | Expiration Time. This is the time at which the assertion token expires. The value must be a Unix timestamp (number of seconds since January 1, 1970). |
Signature
The signature is calculated from the first two parts, header and payload, by using the signing algorithm that is specified in the alg header field as follows:
sign(base64urlEncoded(header) '.' base64urlEncoded(payload))
The signature must be calculated using the signing algorithm specified in the alg header field.
Various libraries exist to calculate the signature. The following example uses Nimbus JWT and an RSA signature with SHA-256 hash (RS256):
Example: compute an encoded assertion token
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.util.io.pem.PemReader;
public class JwtGenerator {
public static void main(String[] args) {
var header = """
{
"alg": "RS256",
"kid": "TKLA3x5rx7QJcwJz_b57M7x7pUrgFYQ6shri1XYqvrM",
"typ": "JWT"
}""";
var payload = """
{
"jti": "99325f42-1c85-4427-b081-81fbeeeb1c91",
"iss": "client_id_123",
"sub": "client_id_123",
"aud": "https://auth-sandbox.abnamro.com",
"scope": "merchant:wero-payment:all",
"iat": 1776757599,
"exp": 1776758199
}""";
try {
var signedJwt = generateSignedJwt(header, payload, "key_prv_pkcs8.pem");
System.out.println("Signed JWT: " + signedJwt);
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException | SignatureException | InvalidKeyException e) {
System.out.println("Error generating signed JWT: " + e.getMessage());
throw new RuntimeException(e);
}
}
public static String generateSignedJwt(String header, String payload, String privateKeyPath)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
// 1. Base64Url encode header and payload
String headerB64 = base64UrlEncode(header.getBytes(StandardCharsets.UTF_8));
// 2. Base64Url encode the payload
String payloadB64 = base64UrlEncode(payload.getBytes(StandardCharsets.UTF_8));
// 3. Create the signing input
String signingInput = headerB64 + "." + payloadB64;
// 4. Read private key and sign the input
PrivateKey privateKey = loadPrivateKey(privateKeyPath);
Signature signature = Signature.getInstance("SHA256withRSA"); // algorithm for RS256
signature.initSign(privateKey);
signature.update(signingInput.getBytes(StandardCharsets.UTF_8));
byte[] sigBytes = signature.sign();
// 5. Base64Url encode the signature
String signatureB64 = base64UrlEncode(sigBytes);
// 6. Combine to form the final JWT
return signingInput + "." + signatureB64;
}
/**
* Helper method to Base64Url encode a byte array without padding.
*/
private static String base64UrlEncode(byte[] input) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(input);
}
/**
* Helper method to load a private key from a PEM file.
*/
private static PrivateKey loadPrivateKey(String filename)throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (PemReader pemReader = new PemReader(new FileReader(filename))) {
byte[] content = pemReader.readPemObject().getContent();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(content);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}
}
}
Requesting an access token
To request an access token, make a POST request to the token endpoint of ABN AMRO's authorization server (e.g., https://auth.abnamro.com/as/token.oauth2 for production environment) with the following parameters:
| Attribute | Value |
|---|---|
| client_id | As supplied to you by ABN AMRO |
| grant_type | client_credentials |
| client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
| scope | Specify the scope for the operation that is to be authorized. Choose one from the scope table OAuth 2.0 Scopes |
| client_assertion | The signed assertion token. Refer to Authentication to generate the token |
Example: curl
curl --location 'https://auth-sandbox.abnamro.com/as/token.oauth2' \
--header 'User-Agent: python-requests/2.32.5' \
--header 'Accept-Encoding: gzip, deflate' \
--header 'Accept: */*' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: PF=dfv3puRBTjbzFSzlKPO78Z' \
--d 'client_id=<client id>' \
--d 'grant_type=client_credentials' \
--d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--d 'client_assertion=<signed assertion token>' \
--d 'scope=<scope>'
OAuth Scopes
| Use | Operation | Request for scope |
|---|---|---|
| Merchant access | Initiate Wero payment and retrieve status | merchant:wero-payment:all |
| Service Provider access | Initiate Wero payment and retrieve status | service-provider:wero-payment:all |
| Payment Facilitator access | Initiate Wero payment and retrieve status | payfac:wero-payment:all |
How to use this API
This tutorial describes how to connect an application to the Wero Merchant Payment API in the sandbox environment, and initiate Wero payment.
Note: Before you start this tutorial, you must complete the steps described in Sandbox access.
Step 1 - Request an access token
Options 1 - using private_key_jwt authentication
Refer Authentication and use following properties to get the access token
| Attribute | Value |
|---|---|
| private key (RSA) | AAB.SYS.027400 |
| client_id | wero_merchant2 |
| client_assertion_type | urn:ietf:params:oauth:client-assertion-type:jwt-bearer |
| aud | https://auth-sandbox.abnamro.com |
| scope | specify the scope for the operation that is to be authorized. refer OAuth-2.0-Scopes |
| client_assertion | signed assertion token |
| Private Key to sign assertion token |
|---|
| Download private key: Download |
Sample request
curl --location 'https://auth-sandbox.abnamro.com/as/token.oauth2' \
--header 'User-Agent: python-requests/2.32.5' \
--header 'Accept-Encoding: gzip, deflate' \
--header 'Accept: */*' \
--header 'Connection: keep-alive' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: PF=dfv3puRBTjbzFSzlKPO78Z' \
--d 'client_id=<client_id>' \
--d 'grant_type=client_credentials' \
--d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--d 'client_assertion=ewogICJhbGciOiAiUl.d121212.qbZe4sH5ZlCfbtNc7A' \
--d 'scope=merchant:wero-payment:all'
Option 2 - Obtaining the access token using client_secret_basic
Warning: This authentication method will be deprecated soon. Use private_key_jwt authentication method to obtain the access token.
Request attributes
The table below provides the required attributes for requesting access token on Sandbox environment.
| Attribute | Value |
|---|---|
| client_id | AAB.SYS.027400 |
| API-Key | The API Key for your application on the Developer Portal |
| grant_type | client_credentials |
| client_secret | OzTBGCKBS3dc2Mo72x8ibTKsGzgBoFLTy4YBl9YOLPVw56l8bCpJYZz2I4PIesz9 |
| scope | You must specify the scope for the operation that is to be authorized. refer OAuth-2.0-Scopes |
Sample request
Request an access token using the following sample:
curl -X POST "https://auth-sandbox.abnamro.com/as/token.oauth2" \
-d "grant_type=client_credentials" \
-d "client_id=AAB.SYS.027400" \
-d "client_secret=OzTBGCKBS3dc2Mo72x8ibTKsGzgBoFLTy4YBl9YOLPVw56l8bCpJYZz2I4PIesz9" \
-d "scope=merchant:wero-payment:all"
If the access token request is valid and authorized, the authorization server issues an access token.
Note: It is recommended that you use the access token for up to 90% of its expiration time (ms).
Sample response
{
"token_type": "Bearer",
"access_token": "X1PTWZre0fnW72l263yrhAWB2FDwx3tg",
"expires_in": 7199
}
Step 2 - Initiate a Wero payment
Payment initiation attributes
To create a Wero merchant payment, using the App Token you created in Step 1, execute: POST Create Wero Payment.
This operation creates a new Wero payment as specified by the body of the request, and returns a paymentId which is used as unique identifier of a payment.
Sample request
curl -X POST -k https://api-sandbox.abnamro.com/wero-merchant-payments/v1 \
-v \
-H 'Accept: application/json' \
-H 'Authorization: Bearer X1PTWZre0fnW72l263yrhAWB2FDwx3tg' \
-H 'Trace-Id: 00000000-0000-0000-1111-000000000001' \
-H 'Content-Type: application/json' \
-H 'API-Key: YourAPIKeyHere' \
-d '{
"payment": {
"type": "SINGLE",
"amount": {
"euroInCents": 1000
},
"acceptor": {
"id": "AcceptorId",
"shop": {
"id": "ShopId"
}
}
},
"returnUrl": "http://merchantUrl",
"orderReference": "8798798755",
"orderDescription": "Lunch with friends"
}'
Sample response
{
"paymentId": "SIN-01KHXN46XPM1C6V56FWW682WC3",
"redirectUrl": "https://dummy.wero.ideal.nl/f6a28050-afb5-4d2c-9240-f9fc4caff44a"
}
Step 3 - Redirect the consumer to the Wero payment page
Use the redirectUrl attribute from the response of Step 2 to direct the consumer to the Wero payment page.
Important: The
redirectUrlvalue in sandbox environment is a dummy URL and will not lead to an actual payment page. In production, theredirectUrlwill lead to the Wero payment page where the consumer can choose their bank and complete the payment flow.
Step 4 - Retrieve the status of the payment
Consumer grants consent to the payment in their consumer PSP and are redirected to the merchant page ('returnUrl' in the request body of GET Payment Status).
ABN AMRO Wero Acquiring after receiving the consent notification from WERO, orchestrates the payment authorization and payment capture.
Merchant calls GET Payment Status using the paymentId received in Step 2 to retrieve the status of the payment. The status endpoint can be polled in regular intervals to check the status of the payment.
Sample request
curl -X GET -k https://api-sandbox.abnamro.com/wero-merchant-payments/v1/SIN-01KHXN46XPM1C6V56FWW682WC3/status \
-v \
-H 'Accept: application/json' \
-H 'Authorization: Bearer X1PTWZre0fnW72l263yrhAWB2FDwx3tg' \
-H 'Trace-Id: bc69c490-9524-413f-971a-21f1aecd9fe5' \
-H 'Content-Type: application/json' \
-H 'API-Key: YourAPIKeyHere'
Sample response
{
"paymentId": "SIN-01KHXN46XPM1C6V56FWW682WC3",
"currentStatus": "PAYMENT_AUTHORISED"
}
Simulated behavior in sandbox
To simulate a few possible payment flows, you can provide specific payment amount values in the POST Create Wero Payment. Use the paymentId returned in the response to retrieve the payment status and check the behavior of the API.
The following amount values can be used in the sandbox environment for testing purposes.
| Amount value | Behavior of Create Wero Payment | Behavior When Retrieving Payment Status |
|---|---|---|
| euroInCents = 111 | Responds with HTTP 200, with a valid paymentId |
Responds with HTTP 200, with currentStatus as PAYMENT_AUTHORISED |
| euroInCents = 222 | Responds with HTTP 200, with a valid paymentId |
Responds with HTTP 200, with currentStatus as CANCELED |
| euroInCents = 333 | Responds with HTTP 200, with a valid paymentId |
Responds with HTTP 200, with currentStatus as EXPIRED |
Need help?