Authentication Signature for OP Online Payment API

Support: onlinepayment@op.fi
The OP Online Payment API uses PKI-based message signatures for authenticating API requests and for verifying the integrity of the data sent between the client and the API. This page describes how the signature can be calculated from the parameters.

The same signature format is used for both merchant API calls to OP and OP originated merchant callbacks to the merchant backend.

See Online Payment REST API documentation

See Online Payment Merchant callback REST API

Structure

The signatures are sent using the HTTP Authorization header with the following format (without the curly braces):

Authorization: {merchantId}:{algorithm}:{keyVersion}:{signature}

The four parts separated by the colon character (':') are:

  • merchantId – merchant calling the API, or OP in case of Merchant Callback API notification (UUID),
  • algorithm – hash digest algorithm. Current options: 1 = SHA-256,
  • keyVersion – merchant key version. Value between 0 and 9999,
  • signature – the hex encoded signature value.

Creating the hash

The string to be hashed has the following structure. For messages without body (i.e. GET) keep the Unix encoded new line (\n) after URL, but omit the body value

String stringToSign = HTTP method name + \n
+ Content-Type + \n
+ Date + \n
+ merchantId + \n
+ x-api-key + \n
+ x-session-id + \n
+ x-request-id + \n
+ Full URL with query parameters + \n
+ Message body;

The HTTP request must contain the date header.

Example value

(as Java string)

String stringToSign = "POST" + "\n"
+ "application/json" + "\n"
+ "Wed, 06 Apr 2020 06:09:55 GMT" + "\n"
+ "f8cef553-77df-48cc-bd1c-fb05dcfb64fa" + "\n"
+ "dxB2AFwnwraQRrAsLZpJ5T4IrNGp7fhx" + "\n"
+ "1ec65769-2d9c-4cc8-8031-7327747c4d4c" + "\n"
+ "b35e55a2-ef71-4675-b8cb-a154c650843b" + "\n"
+ "https://sandbox.apis.op.fi/paymentbutton/v1/payments" + "\n"
+ "{\"amount\":\"1.00\",\"cancel\":{\"url\":\"https://shop.example.com/cancel/path\"},\"reject\":{\"url\":\"https://shop.example.com/reject/path\"},\"return\":{\"url\":\"https://shop.example.com/return/path\"},\"currency\":\"EUR\",\"accountId\":\"8a5d4aac-8f8f-47ed-ae2f-36ffeaf57c79\",\"reference\":\"RF3517834735\"}";

For example, with the following private key:

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCq4FH4inYm9rsb
PIIFb/tH1E/eYX3sXG2JEYFvvjSZIoYUCq8lzzKyfP+GrFjzzqNjpg5naQtyvUi6
7yCIFRP8rhpEi0zpBoSip989VeWxp6PJ3y+V6xYVI8a7dft/5jKnj6B/rtPkZL28
Sb1dIhq3DqAiddWCoqT6gcm+B/w2AUZ97v4XMYBh3P46+UHkNIVZxd2zh8HwIpkf
vySRFxSGj+tnFT8y6Z9GBNz/RaCmqAxfYt6JTPOmvX2Ep4l+LH8h8947epaI5ZdH
ktZO4br87KxU9Rnw56mAwfkPaWAINDnmogQdXndduSjCH696Ex7JgFfts8pSAkEM
WDQOWlC9AgMBAAECggEAToZZhRL0KwG1iFWtFpyYaDDsJzC8MnNjLtsplVVeTIUu
AmXKiWCHVrjUoGnxUtFCCpgUBHekeCz+EFG1rHOrRLIphhhR1sBVEX59u20O6i40
uZ9j+cwX0M0pFZqpYeRCoPgC9Mo9u7aD7tQgPn1es4L4Gf32iVr+39PnBvoacCIz
nxAUdY4MW/PgdDZRwtO/AR+KEA9bUBro0TOCvTMpeR9EJ9spD0/SfuCDfI+WdofY
2hW7TZbboRt1dszoHcdQelZkFdOM/oAE2MYMzlBAbygJJUkjyAoI9Bez1997DhJk
01sDbNZTpGJP59aP379R1uoW3rTHmUANxmRcpyZrrQKBgQDWJE8hkvISNCXROgMZ
ea5IGGuseK5o2HDuVAwmGbSf5NfzH5AmET7laYZNx2oniSv1LHm24rucNttc/6RA
5qz/nvEbG07AW2DFal7EXYbBZLcQPe9chVUExeWbBs88rhKH4CQ1t6c6y1CzYhSV
wRRXKddw4qXdPvD1GHp5Jsl0MwKBgQDMRv6d8ymPZbRIQ1mThGUFoZY15DHQk2DW
76tPczY7Xz/Z9UuCmQcH+Nenv8QfdnZF81DhGtt8nfzQECqJwcHdhthx2Fbbin5k
BJpFd8KAN4zCcKVbFH+ewNKGT6Kn6GDcc6+nNnch9dXG4dQkB93lZdeWXHawL/rJ
udwW1GW3TwKBgGqjUjSp9JpUFbEHbpu1GLEWWChfQJs9jZ9hg1tF2cj2MQQFZ8dN
N0EPN65r69UcXiONrl8AseSs/LhnJeib9vKkt/SDuMfZuWsV+XNYD88m1HLmJNiy
HRBvbFOzJGhXVysK131Yo5KHxPxPj2iz6ekuEPdKJsbynROwyOykABY5AoGAaxY8
nCjA/L9gRxGnf8HEA7O1vwKlaqYX+hUiRUAsietg2a3Rq+D04qT8yJ+q/KNpVTo8
iAVAUo+v3JLc+eJs8uihxuyWe/iaUWxoQ0qI2BZG4BeVV63jSSHkOyy8JDGZtXef
+ZR/13m8W8o/H7RQCtXcsqI+Rhag7edVDVLDD9kCgYEAzjrCOi45+wmaWib/yGKU
kCLNmC2NYfyxgHXHsTKfC06CWpuchQBJnW06NxXjnSoZ6kPl4JPJv17gvd+UcXaR
64nLLmZQl9ij4UW/F6giq2T2hZu2yu2FQdASSY7PYPtIZZX6BuSxAWR++tGEidEw
Gf4XybYqmS2/BrmS9Pvj164=
-----END PRIVATE KEY-----

the resulting string is as follows:

f8cef553-77df-48cc-bd1c-fb05dcfb64fa:1:0:46bdcd4bec165dc2a15c05455be05fdc00d17a40555265ee1406911dca125cfe82acbf54a1bdb6f959ba57621ab295d14bcae5d851103b14e09c49e655862b9ee5a2fd862aec9a7aff23ed49604c4a87ad8ca6fd7630194a56164d3bfe3d55b102c98b17c0fd5426292722e90bedb0c3a014e511c665efb0d91f324ddd9cda1574dea8b725f53ddccda77c0eea2921955e9e077875aebb02b8896b4eea076f99d472b9394091d19f0f03b94f89046d16441f2c74ef7db03c81b352b5e26ed37faf047c840ff77db13ae2ad0874fde4d9c415bc0ab62e357630a9bc75e273162d2386f475cec5332f7d919610724587b357814469b0c8aa4f9dc531933450ccbe

This value is now ready to be used in the Authorization header.

The public key that matches the private key in this example is:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAquBR+Ip2Jva7GzyCBW/7
R9RP3mF97FxtiRGBb740mSKGFAqvJc8ysnz/hqxY886jY6YOZ2kLcr1Iuu8giBUT
/K4aRItM6QaEoqffPVXlsaejyd8vlesWFSPGu3X7f+Yyp4+gf67T5GS9vEm9XSIa
tw6gInXVgqKk+oHJvgf8NgFGfe7+FzGAYdz+OvlB5DSFWcXds4fB8CKZH78kkRcU
ho/rZxU/MumfRgTc/0WgpqgMX2LeiUzzpr19hKeJfix/IfPeO3qWiOWXR5LWTuG6
/OysVPUZ8OepgMH5D2lgCDQ55qIEHV53Xbkowh+vehMeyYBX7bPKUgJBDFg0DlpQ
vQIDAQAB
-----END PUBLIC KEY-----

Creating signature with OpenSSL

Download the following files and generate a signature with: create-signature.zip

openssl dgst -sha256 -sign private-key.pem -hex signature-base.txt

Verifying signature with OpenSSL

Download the following files and generate a signature with: verify-signature.zip

openssl dgst -verify public-key.pem -keyform PEM -sha256 -signature signature-result.binary  -hex signature-base.txt

Note that the signature result in the file is stored in the binary representation of the signature and hex format can inspected, for example, with:

cat signature-result.binary |xxd -p | tr -d '\n'

Production use: generating keys, acquiring merchant ID and account ID

Once production access has been requested and granted, the registered party will receive a message through a secure email.

This message will request the public key, which should be sent as a response in PEM format.

To generate a key without a company PKI infrastructure in place, directly with OpenSSL in the raw form:

openssl genrsa -out payment-button.private 2048
openssl rsa -in payment-button.private -out payment-button.public -pubout -outform PEM

If a company uses a PKI service or hardware security device where a public key is delivered as part of certificate, the raw public key can be extracted with the following command:

openssl x509 -pubkey -noout -in <name-of-certificate> > payment-button.public

 The payment-button.public should be delivered to OP as a response to the request outlined above.

After the public key has been received, the merchant ID and account ID will be sent to the registered party in a follow-up email.

Verify the resulting public key

In both cases the resulting file (payment-button.public) is a PEM formatted text file starting exactly with:

-----BEGIN PUBLIC KEY-----

and ending exactly with:

-----END PUBLIC KEY-----

If the content is something else, i.e. putty has been used to create the keypair and content is exported, the format cannot be read.

Complete example of initiating a payment in the Sandbox

An example BASH script is provided to illustrate how to build a request to initiate a new payment with the OP Online Payment API in the sandbox. An application registered in the sandbox is required, with its API key set in the variable _API_KEY_ in the script after downloading it.

The script will show

  • intermediate results for values used in the request;
  • how to build the signature base;
  • how to calculate the Authorization header value; and
  • what headers and values are needed to make the request work.

Running the script can help identifying differences in the Authorization header calculation between this reference implementation and an implementation using other tooling. See also an example script which illustrates the same in Python.

Change log

v1.4 (03/2024)

Changed

  • Updated API doc format from Swagger 2.0 to OpenAPI 3.0.1

v1.3 (4/2023)

Added

  • Added test for Python script location

v1.2 (10/2021)

Added/changed

  • Multiple revisions to the text

v1.1 (6/2021)

Added/changed

  • Multiple revisions to the text

v1.0 (2/2021)

Added

  • Initial version