OP Corporate Payment API (1.6.1)

Download OpenAPI specification:Download


OP Corporate Payment API allows OP corporate customers to initiate payments from their accounts.

Authentication

Authentication in OP Corporate Payment API is based on practices from OpenID Connect (OIDC) 1.0 and OAuth 2.0.

Security Considerations

It is extremely important that the client_id, client_secret and client certificate private key are not exposed at any point. Together they represent the identity of the client and thereby the corporation. In the possession of an attacker they could be used to make fraudulent API calls potentially causing considerable damage.

Sandbox

To test the API in the sandbox environment, please send an email to corp-payment-APIs@op.fi.

Production access

You can get production access for this API on OP API Admin.

To use these APIs against production you should replace host in the API examples below with https://corporate-api.apiauth.services.op.fi

Payment request signature

To ensure that the payment request payload is not altered and evidence of non-repudiation is maintained, a signature is calculated over the payment request body. The signature is a detached content JWS, as defined in RFC7515 with the following mandatory JWS headers:

  • alg - string - only RS256 algorithm is currently supported for the signature algorithm
  • kid - string - The signature Key Identifier (KID) received from the registration UI, used to map the signing key to the correct public key at the verifying end.
  • b64 - boolean - The boolean value for this must be false. This indicates that the detached message payload is not base64 URL encoded when calculating the request signature. See RFC 7797 - The "b64" header Parameter
  • urn:op.api.iat - number - This must be a JSON number representing the number of seconds from 1970-01-01T0:0:0Z as measured in GMT until the date/time.
  • crit - array of string - This must be an array containing the critical header names, ["b64","urn:op.api.iat"]

The signature is computed in the detached mode, where the payload is not base64 URL encoded when forming the signature base string, but the UTF-8 encoded bytes of the sent request body are used as-is to construct the signature base string. See the example code below for more details.When sending the payment request, the signature must be included in the request header "X-Req-Signature", and it will be validated against the key matching the kid parameter.

Change log

v1.6.1 (01/2024)

Added

  • Code examples added for all active endpoints

    Changed

  • Removed section Usage example
  • New response error code 503 added for endpoints SEPA Instant Payment and Payment V2
  • PHP code example for Payment V2 fixed

v1.6 (11/2023)

Added

  • New endpoint Payment V2 added in the sandbox environment. The endpoint allows for faster, asynchronous payment processing. Endpoints SEPA Payment and SEPA Instant Payment are not impacted by this new endpoint introduction
  • New endpoint Payment V2 Status Query added to support status queries for payments initiated with Payment V2 in the sandbox environment.
  • Code examples for calling POST endpoint on Payment V2. Please see the request samples section of the endpoint on the right select Code examples of desired language.

    Changed

  • New response parameter instructionId added into SEPA Payment and SEPA Instant Payment responses

v1.5 (01/2023)

Added

  • Request parameter ultimateCreditor added to endpoints SEPA Payment and SEPA Instant Payment. Using this parameter, API users will be able to add information on the ultimate payee (ultimate creditor)

    Changed

  • Request parameter endToEndId now allows for more special characters

v1.4 (09/2022)

Added

  • Request parameter ultimateDebtor added to endpoints SEPA Payment and SEPA Instant Payment. Using this parameter, API users will be able to add information on the ultimate payer (ultimate debtor)

v1.3 (04/2022)

Changed

v1.2 (12/2021)

Added

  • Corporate Payment API now supports OP Corporate Service Provider API usage

v1.1 (09/2021)

Changed

  • Improved the description for creditor and debtor address request parameters
  • Improved the way in which IBAN account numbers are handled

v1.0 (12/2019)Initial version

SEPA Payment

Initiates a SEPA payment. Primarily payments are initiated as SEPA Instant payments. If a payment cannot be processed as a SEPA Instant payment, a SEPA payment is initiated as a fallback.

header Parameters
authorization
required
string

Bearer JWT token

X-Req-Signature
required
string

Body signature

X-Request-ID
string

Unique identifier for a specific request. Used for debugging purposes

Request Body schema: application/json; charset=UTF-8
required

payment info

required
object (Creditor)

Creditor for the payment

required
object (Debtor)

Debtor for the payment

required
object (InstructedAmount)
instructionId
required
string^[a-zA-Z0-9]{1,35}$

Unique identification, as assigned by the original instructing party for the original instructed party, to unambiguously identify the original instruction. This is used to check for duplicate payments, for example, in cases where the end-user has not received a response from the server. In this case the end user can initiate the same payment with the same instructionId and the server can check if the payment has already been processed based on the value of the instructionId.

object (UltimateDebtor)

Ultimate debtor for the payment

message
string [ 1 .. 140 ] characters

Free form message from debtor to creditor. Either message or reference should be given to an outgoing payment.

object (UltimateCreditor)

Ultimate creditor for the payment

reference
string [ 1 .. 25 ] characters

Structured creditor reference, either international RF reference (ISO 11649) or Finnish reference number (viitenumero). Either reference or message should be given for an outgoing payment.

endToEndId
string^[0-9A-Za-z-åäöÅÄÖ_=:.,+]{1,35}$

Unique identification, as assigned by the original initiating party, to unambiguously identify the original transaction.

Responses

Request samples

Content type
application/json; charset=UTF-8
{
  • "debtor": {
    },
  • "ultimateDebtor": {
    },
  • "message": "Less money, fewer problems",
  • "creditor": {
    },
  • "ultimateCreditor": {
    },
  • "reference": "00000000000000482738",
  • "endToEndId": "544652-end2end",
  • "instructionId": "AtoZatoz01234567898NoLongerThan35",
  • "instructedAmount": {
    }
}

Response samples

Content type
application/json; charset=UTF-8
{
  • "amount": "3.45",
  • "status": "PROCESSED",
  • "currency": "EUR",
  • "archiveId": "20190524593156999999",
  • "debtorIban": "FI4550009420888888",
  • "ultimateDebtorName": "Ultimate Debtor",
  • "bookingDate": "2019-05-12T00:00:00.000Z",
  • "paymentType": "SCT_INST",
  • "creditorIban": "FI4550009420999999",
  • "creditorName": "Cedric Creditor",
  • "ultimateCreditorName": "Ultimate Creditor",
  • "transactionId": "A_50009420112088_2019-05-24_20190524593156999999_0",
  • "transactionDate": "2019-05-11T00:00:00.000Z",
  • "endToEndId": "544652-end2end"
}

SEPA Instant Payment

Initiates a SEPA Instant payment. Read more about the payment type on op.fi.

Note: We do our best to ensure a stable service, so that our customers can benefit from real-time payments. However, it's possible that the SEPA Instant payment service may occasionally be unavailable. This means that payments cannot be processed as SEPA Instant payments at that moment, but you can try again later or initiate a SEPA payment.

header Parameters
authorization
required
string

Bearer JWT token

X-Req-Signature
required
string

Body signature

X-Request-ID
string

Unique identifier for a specific request. Used for debugging purposes

Request Body schema: application/json; charset=UTF-8
required

Payment info

required
object (Creditor)

Creditor for the payment

required
object (Debtor)

Debtor for the payment

required
object (InstructedAmount)
instructionId
required
string^[a-zA-Z0-9]{1,35}$

Unique identification, as assigned by the original instructing party for the original instructed party, to unambiguously identify the original instruction. This is used to check for duplicate payments, for example, in cases where the end-user has not received a response from the server. In this case the end user can initiate the same payment with the same instructionId and the server can check if the payment has already been processed based on the value of the instructionId.

object (UltimateDebtor)

Ultimate debtor for the payment

message
string [ 1 .. 140 ] characters

Free form message from debtor to creditor. Either message or reference should be given to an outgoing payment.

object (UltimateCreditor)

Ultimate creditor for the payment

reference
string [ 1 .. 25 ] characters

Structured creditor reference, either international RF reference (ISO 11649) or Finnish reference number (viitenumero). Either reference or message should be given for an outgoing payment.

endToEndId
string^[0-9A-Za-z-åäöÅÄÖ_=:.,+]{1,35}$

Unique identification, as assigned by the original initiating party, to unambiguously identify the original transaction.

Responses

Request samples

Content type
application/json; charset=UTF-8
{
  • "debtor": {
    },
  • "ultimateDebtor": {
    },
  • "message": "Less money, fewer problems",
  • "creditor": {
    },
  • "ultimateCreditor": {
    },
  • "reference": "00000000000000482738",
  • "endToEndId": "544652-end2end",
  • "instructionId": "AtoZatoz01234567898NoLongerThan35",
  • "instructedAmount": {
    }
}

Response samples

Content type
application/json; charset=UTF-8
{
  • "amount": "3.45",
  • "archiveId": "20190524593156999999",
  • "bookingDate": "2019-05-12T00:00:00.000Z",
  • "creditorIban": "FI4550009420999999",
  • "creditorName": "Cedric Creditor",
  • "currency": "EUR",
  • "debtorIban": "FI4550009420888888",
  • "endToEndId": "544652-end2end",
  • "paymentType": "SCT_INST",
  • "status": "PROCESSING",
  • "transactionDate": "2019-05-11T00:00:00.000Z",
  • "transactionId": "A_50009420112088_2019-05-24_20190524593156999999_0",
  • "ultimateCreditorName": "Ultimate Creditor",
  • "ultimateDebtorName": "Ultimate Debtor"
}

SEPA Payment V2

Initiates a SEPA payment asynchronously with improved performance. Final status and other details for the payment should be fetched using the endpoint Payment V2 Status Query. Payments are initiated primarily as SEPA Instant payments. If the payment cannot be processed as a SEPA Instant payment, a SEPA payment is initiated. You can also initiate only SEPA Instant payments with parameter forceSepaInst. For code examples, please see 'Request samples' on the right. Currently available only in the sandbox environment.

header Parameters
authorization
required
string

Bearer JWT token

X-Req-Signature
required
string

Body signature

X-Request-ID
string

Unique identifier for a specific request. Used for debugging purposes

Request Body schema: application/json; charset=UTF-8
required

Payment info

required
object (Creditor)

Creditor for the payment

required
object (Debtor)

Debtor for the payment

required
object (InstructedAmount)
instructionId
required
string^[a-zA-Z0-9]{1,35}$

Unique identification, as assigned by the original instructing party for the original instructed party, to unambiguously identify the original instruction. The same value should be used to request payment status and cannot be used to make more than one payment.

endToEndId
string^[0-9A-Za-z-åäöÅÄÖ_=:.,+]{1,35}$

Unique identification, as assigned by the original initiating party, to unambiguously identify the original transaction.

forceSepaInst
boolean
Default: false

When set to true, forces the payment to be processed only as a SEPA Instant payment. When set to false, a SEPA payment may be initiated if the payment cannot be processed as a SEPA Instant payment.

message
string [ 1 .. 140 ] characters

Free form message from debtor to creditor. Either message or reference should be given to an outgoing payment.

reference
string [ 1 .. 25 ] characters

Structured creditor reference, either international RF reference (ISO 11649) or Finnish reference number (viitenumero). Either the reference or the message should be given for an outgoing payment.

object (UltimateCreditor)

Ultimate creditor for the payment

object (UltimateDebtor)

Ultimate debtor for the payment

Responses

Request samples

Content type
application/json; charset=UTF-8
{
  • "creditor": {
    },
  • "debtor": {
    },
  • "endToEndId": "544652-end2end",
  • "forceSepaInst": false,
  • "instructedAmount": {
    },
  • "instructionId": "AtoZatoz01234567898NoLongerThan35",
  • "message": "Less money, fewer problems",
  • "reference": "00000000000000482738",
  • "ultimateCreditor": {
    },
  • "ultimateDebtor": {
    }
}

Response samples

Content type
application/json; charset=UTF-8
{
  • "amount": "3.45",
  • "creditorIban": "FI4550009420999999",
  • "creditorName": "Cedric Creditor",
  • "currency": "EUR",
  • "debtorIban": "FI4550009420888888",
  • "endToEndId": "544652-end2end",
  • "forceSepaInst": false,
  • "instructionId": "AtoZatoz01234567898NoLongerThan35",
  • "status": "PROCESSING",
  • "ultimateCreditorName": "Ultimate Creditor",
  • "ultimateDebtorName": "Ultimate Debtor"
}

SEPA Instant Payment Status Query

Get the status of a SEPA Instant payment initiated with SEPA Instant Payment or a SEPA payment initiated with SEPA Payment. Should be only used to query payments that ended up with paymentType=SCT_INST and status=PROCESSING. For other payment type and status combinations, the result list may be empty.

path Parameters
instructionId
required
string

instructionId used when the payment was initiated

header Parameters
authorization
required
string

Bearer JWT token

X-Request-ID
string

Unique identifier for a specific request. Used for debugging purposes

Responses

Request samples

## OP Corporate Payment API - SEPA Instant Payment Status Query bash script example

#!/bin/bash

# To run this script you need openssl and jq installed.

# Steps for registering the required keys and certificates
# 1. Valid Corporate API contract created in OP API Admin
# 2. OAuth clientId and clientSecret provisioned
# 3. MTLS private key generated: openssl genrsa -out sandbox-mtls.key 4096
# 4. MTLS certificate signing request (CN and other attributes are ignored): openssl req -new -key sandbox-mtls.key -out sandbox-mtls.csr
# 5. Valid MTLS certificate downloaded from OP API Admin as file "sandbox-mtls.crt" by uploading the certificate signing request (CSR) from the previous step
# 6. Payment signing private key generated: openssl genrsa -out sandbox-signing.key 4096
# 7. Signing csr request: openssl req -new -key sandbox-signing.key -out sandbox-signing.csr
# 8. Signing KID (key identifier) received from the OP API Admin by submitting the sandbox-signing.csr.

# OAuth credentials
clientId="TODO put here oauth client id"
clientSecret="TODO and here client secret"

# MTLS credentials
mtlsKey="sandbox-mtls.key"
mtlsCertificate="sandbox-mtls.crt"

# Payment signing credentials
signingKey="sandbox-signing.key"
signingKid="TODO and here signing KID"

API_SERVER="https://sandbox-api.apiauth.aws.op-palvelut.net"

echo "Getting access token"
reply=$(curl -s ${API_SERVER}/corporate-oidc/v1/token \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d "grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}")
    
token=$(echo $reply | jq -r .access_token)
echo "Access token is: $token"\

## Initiate payment processing with payload signature# Instruction id must be unique for each payment request
INSTRUCTIONID=$(date +%s)
DEBTORIBAN="TODO Your account IBAN from the API contract"
CREDITORIBAN="TODO any valid creditor IBAN"

# Sample payment request with dummy data
paymentRequest="{\"instructionId\":\"$INSTRUCTIONID\",\"endToEndId\":\"endToEndId\",\"creditor\":{\"name\":\"Creditor Name\",\"iban\":\"$CREDITORIBAN\",\"address\":{\"addressLine\":[\"a1\",\"a2\"],\"country\":\"FI\"}},\"debtor\":{\"name\":\"Debtor Name\",\"iban\":\"$DEBTORIBAN\",\"address\":{\"addressLine\":[\"a1\",\"a2\"],\"country\":\"FI\"}},\"instructedAmount\":{\"currency\":\"EUR\",\"amount\":\"0.16\"},\"reference\":\"00000000000000482738\"}"

## Signing the payload is mandatory for payment requests
# Issued at, in unix timestamp format
IAT=$(date +%s)

# Form the JWS header with mandatory fields
HEADER="{\"b64\":false,\"crit\":[\"b64\",\"urn:op.api.iat\"],\"alg\":\"RS256\",\"urn:op.api.iat\":${IAT},\"kid\":\"${signingKid}\"}"

# Base64 encode the header. JWT encoding is slightly different from default
# output of base64, so make the necessary changes to make the output URL-safe,
# and remove trailing padding (=).
HEADER_ENC=$(echo -n "${HEADER}" | base64 | tr -- '+/=' '-_ ' | tr -d '[:space:]')

# Sign the header and payload. The signature calculation in the detached body JWS is slightly different from the standard way, as the payload is not base64-encoded
# before signing, but included as-is in the signature plain text. The signing algorithm is RS256, also known as "RSA Digital Signature Algorithm with SHA-256".# The resulting signature is base64-encoded, and the same URL-safe transformations are applied.
SIGNATURE=$(echo -n "${HEADER_ENC}.${paymentRequest}" | openssl dgst -sha256 -sign ${signingKey} -binary | openssl base64 -e -A | tr -- '+/=' '-_ ' | tr -d '[:space:]')

# The signature header includes the base64-encoded JWS header, followed by two dots (..), and the base64-urlsafe-encoded signature. The actual message payload is not
# repeated in the signature
REQ_SIGNATURE="${HEADER_ENC}..${SIGNATURE}"

# The signature is included in the "X-Req-Signature" header.
echo "Sending payment request"
paymentResponse=$(curl -s ${API_SERVER}/corporate-payment/v1/sepa-instant-payment \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $token" \
    -H "X-Req-Signature: ${REQ_SIGNATURE}" \
    -d "$paymentRequest")
echo $paymentResponse | jq -C .

echo "Sending instant payment status query request"
paymentStatusQueryResponse=$(curl -X GET ${API_SERVER}/corporate-payment/v1/sepa-instant-payment/$INSTRUCTIONID \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Accept: application/json' \
    -H "Authorization: Bearer $token")
echo $paymentStatusQueryResponse | jq -C .           

Response samples

Content type
application/json; charset=UTF-8
[
  • {
    }
]

SEPA Payment V2 Status Query

Get the status of a payment initiated with Payment V2. Currently available only in the sandbox environment.

path Parameters
instructionId
required
string

the instruction identifier used to initiate the payment

header Parameters
authorization
required
string

Bearer JWT token

X-Request-ID
string

Unique identifier for a specific request. Used for debugging purposes

Responses

Request samples

## OP Corporate Payment API - SEPA Payment V2 Status Query bash script example

#!/bin/bash

# To run this script you need openssl and jq installed.

# Steps for registering the required keys and certificates
# 1. Valid Corporate API contract created in OP API Admin
# 2. OAuth clientId and clientSecret provisioned
# 3. MTLS private key generated: openssl genrsa -out sandbox-mtls.key 4096
# 4. MTLS certificate signing request (CN and other attributes are ignored): openssl req -new -key sandbox-mtls.key -out sandbox-mtls.csr
# 5. Valid MTLS certificate downloaded from OP API Admin as file "sandbox-mtls.crt" by uploading the certificate signing request (CSR) from the previous step
# 6. Payment signing private key generated: openssl genrsa -out sandbox-signing.key 4096
# 7. Signing csr request: openssl req -new -key sandbox-signing.key -out sandbox-signing.csr
# 8. Signing KID (key identifier) received from the OP API Admin by submitting the sandbox-signing.csr.

# OAuth credentials
clientId="TODO put here oauth client id"
clientSecret="TODO and here client secret"

# MTLS credentials
mtlsKey="sandbox-mtls.key"
mtlsCertificate="sandbox-mtls.crt"

# Payment signing credentials
signingKey="sandbox-signing.key"
signingKid="TODO and here signing KID"

API_SERVER="https://sandbox-api.apiauth.aws.op-palvelut.net"

echo "Getting access token"
reply=$(curl -s ${API_SERVER}/corporate-oidc/v1/token \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d "grant_type=client_credentials&client_id=${clientId}&client_secret=${clientSecret}")
    
token=$(echo $reply | jq -r .access_token)
echo "Access token is: $token"\

## Initiate payment processing with payload signature# Instruction id must be unique for each payment request
INSTRUCTIONID=$(date +%s)
DEBTORIBAN="TODO Your account IBAN from the API contract"
CREDITORIBAN="TODO any valid creditor IBAN"

# Sample payment request with dummy data
paymentRequest="{\"instructionId\":\"$INSTRUCTIONID\",\"endToEndId\":\"endToEndId\",\"creditor\":{\"name\":\"Creditor Name\",\"iban\":\"$CREDITORIBAN\",\"address\":{\"addressLine\":[\"a1\",\"a2\"],\"country\":\"FI\"}},\"debtor\":{\"name\":\"Debtor Name\",\"iban\":\"$DEBTORIBAN\",\"address\":{\"addressLine\":[\"a1\",\"a2\"],\"country\":\"FI\"}},\"instructedAmount\":{\"currency\":\"EUR\",\"amount\":\"0.16\"},\"reference\":\"00000000000000482738\"}"

## Signing the payload is mandatory for payment requests
# Issued at, in unix timestamp format
IAT=$(date +%s)

# Form the JWS header with mandatory fields
HEADER="{\"b64\":false,\"crit\":[\"b64\",\"urn:op.api.iat\"],\"alg\":\"RS256\",\"urn:op.api.iat\":${IAT},\"kid\":\"${signingKid}\"}"

# Base64 encode the header. JWT encoding is slightly different from default
# output of base64, so make the necessary changes to make the output URL-safe,
# and remove trailing padding (=).
HEADER_ENC=$(echo -n "${HEADER}" | base64 | tr -- '+/=' '-_ ' | tr -d '[:space:]')

# Sign the header and payload. The signature calculation in the detached body JWS is slightly different from the standard way, as the payload is not base64-encoded
# before signing, but included as-is in the signature plain text. The signing algorithm is RS256, also known as "RSA Digital Signature Algorithm with SHA-256".# The resulting signature is base64-encoded, and the same URL-safe transformations are applied.
SIGNATURE=$(echo -n "${HEADER_ENC}.${paymentRequest}" | openssl dgst -sha256 -sign ${signingKey} -binary | openssl base64 -e -A | tr -- '+/=' '-_ ' | tr -d '[:space:]')

# The signature header includes the base64-encoded JWS header, followed by two dots (..), and the base64-urlsafe-encoded signature. The actual message payload is not
# repeated in the signature
REQ_SIGNATURE="${HEADER_ENC}..${SIGNATURE}"

# The signature is included in the "X-Req-Signature" header.
echo "Sending payment request"
paymentResponse=$(curl -s ${API_SERVER}/corporate-payment/v2/payment \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $token" \
    -H "X-Req-Signature: ${REQ_SIGNATURE}" \
    -d "$paymentRequest")
echo $paymentResponse | jq -C .

echo "Sending payment status query request"
paymentStatusQueryResponse=$(curl -X GET ${API_SERVER}/corporate-payment/v2/payment/$INSTRUCTIONID \
    --key ${mtlsKey} \
    --cert ${mtlsCertificate} \
    -H 'Accept: application/json' \
    -H "Authorization: Bearer $token")
echo $paymentStatusQueryResponse | jq -C .            

Response samples

Content type
application/json; charset=UTF-8
{
  • "amount": "3.45",
  • "archiveId": "20190524593156999999",
  • "bookingDate": "2019-05-12T00:00:00.000Z",
  • "creditorIban": "FI4550009420999999",
  • "creditorName": "Cedric Creditor",
  • "currency": "EUR",
  • "debtorIban": "FI4550009420888888",
  • "endToEndId": "544652-end2end",
  • "instructionId": "AtoZatoz01234567898NoLongerThan35",
  • "paymentType": "SCT_INST",
  • "status": "PROCESSED",
  • "transactionDate": "2019-05-11T00:00:00.000Z",
  • "ultimateCreditorName": "Ultimate Creditor",
  • "ultimateDebtorName": "Ultimate Debtor"
}