Initiate your first payment
This tutorial presents how to initiate a domestic payment.
Similar steps apply to all of our domestic and international payment endpoints, including scheduled payments and standing orders; therefore, after you complete this tutorial, you will know how to use all of them.
For detailed information on requests, responses, their parameters and how they differ depending on the payment type, see the Open Banking API reference.
To learn how to initiate multiple payments in a batch, see the tutorial Create a file payment.
If you get certificate errors when using curl with Sandbox, it usually means your system doesn't trust our certificate issuer. The recommended approach is to add the certificate to your trusted store.
As a quick workaround, you can use the -k (or --insecure) option to skip certificate checks. Be aware this disables all SSL verification, which can hide issues like expired or mismatched certificates and leaves you vulnerable to man-in-the-middle attacks. For these reasons, avoid using -k in production or as a permanent solution.
Prerequisites
Before you begin, ensure that you have:
- Registered your application with the
paymentsscope in the Developer Portal - Obtained a sandbox/production
client_idfrom the Developer Portal - Obtained sandbox/production
transport.pemandsigning.pemcertificates from the Developer Portal or from a QTSP issuing body - Uploaded in the Developer Portal the
jwks_urlspecific to yoursigning.pemcertificate. Ensure that thex5cclaim is specified.
If unsure, head to Get Started and review the setup process.
1. Generate a client credentials token
Request an access token for client credentials using the /token endpoint and the client_credentials grant type:
curl --cert transport.pem --key private.key \
--location --request POST 'https://oba-auth.revolut.com/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=payments' \
--data-urlencode 'client_id=<your client_id>'
Response:
{
"access_token": "<JWT client credentials>",
"token_type": "Bearer",
"expires_in": 2399
}
Use this token to:
- Create a payment consent
- Check the status of a previously created payment consent
- Check the status of a previously completed payment
When your token expires and you need a new one, repeat this procedure to generate a new token.
2. Create a domestic payment consent
Create a consent to initiate a domestic payment on behalf of a Revolut customer.
Consent prerequisites
When you create the consent, ensure that you:
-
Use your access token that you obtained with
grant_type=client_credentialsas the Bearer token. -
Specify the
x-idempotency-keyheader so that when there is a network failure and you do not receive the ID from the response, you can safely retry the request. -
Provide the
x-jws-signatureheader in the API request.It must contain a valid JSON Web Signature (JWS) of the request payload body. When generating the signature, use the following JSON header:
{
"alg": "PS256",
"kid": "<kid parameter of your signing certificate>",
"crit": ["http://openbanking.org.uk/tan"],
"http://openbanking.org.uk/tan": "<root domain of your JWKS URL>"
}The result is a string consisting of the above header in base64 encoding, followed by two dots and the cryptographic signature.
warningThe signature in the JWS validates the header and payload as text, so make sure the payload used when generating the signature is exactly the same as the payload sent in the request payload, including JSON formatting, line breaks and spacing.
You can read more about JSON Web Signatures here.
Create a consent
Make a domestic payment consent request. An example request looks like this:
curl --cert transport.pem --key private.key \
--location --request POST 'https://oba-auth.revolut.com/domestic-payment-consents' \
--header 'x-fapi-financial-id: 001580000103UAvAAM' \
--header 'Content-Type: application/json' \
--header 'x-idempotency-key: 123' \
--header 'Authorization: Bearer <insert JWT client credentials from step 1.>' \
--header 'x-jws-signature: <insert JWS>' \
--data '
{
"Data": {
"Initiation": {
"InstructionIdentification": "ID412",
"EndToEndIdentification": "E2E123",
"InstructedAmount": {
"Amount": "55.00",
"Currency": "GBP"
},
"CreditorAccount": {
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "11223321325698",
"Name": "Receiver Co."
},
"RemittanceInformation": {
"Unstructured": "Shipment fee"
}
}
},
"Risk": {
"PaymentContextCode": "EcommerceGoods",
"MerchantCategoryCode": "5967",
"MerchantCustomerIdentification": "1238808123123",
"DeliveryAddress": {
"AddressLine": ["7"],
"StreetName": "Apple Street",
"BuildingNumber": "1",
"PostCode": "E2 7AA",
"TownName": "London",
"Country": "UK"
}
}
}
Response:
{
"Data": {
"Status": "AwaitingAuthorisation",
"StatusUpdateDateTime": "2020-11-20T08:35:53.523806Z",
"CreationDateTime": "2020-11-20T08:35:53.523806Z",
"ConsentId": "6686e444-c103-4077-8085-8e094200c425",
"Initiation": {
"InstructionIdentification": "ID412",
"EndToEndIdentification": "E2E123",
"InstructedAmount": {
"Amount": "55.00",
"Currency": "GBP"
},
"CreditorAccount": {
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "11223321325698",
"Name": "ReceiverCo."
},
"RemittanceInformation": {
"Unstructured": "Shipment fee"
}
}
},
"Risk": {
"PaymentContextCode": "EcommerceGoods",
"MerchantCategoryCode": "5967",
"MerchantCustomerIdentification": "1238808123123",
"DeliveryAddress": {
"AddressLine": [
"7"
],
"StreetName": "AppleStreet",
"BuildingNumber": "1",
"PostCode": "E27AA",
"TownName": "London",
"Country": "UK"
}
},
"Links": {
"Self": "https://oba-auth.revolut.com/domestic-payment-consents/6686e444-c103-4077-8085-8e094200c425"
},
"Meta": {
"TotalPages": 1
}
}
For the full list of available parameters and other details:
3. Create a JWT URL parameter
After you create a consent, you need the user to authorise the consent so that you can initiate a payment on their behalf.
Create a JWT request parameter with the following header and payload. Use the private key corresponding to your signing certificate. This signature will be validated using the JWKS endpoint that you specified when you registered your application.
- The values of
client_id,redirect_uri,kid, andscopeshould correspond to those for your specific application and consent request. - The value of
openbanking_intent_idis the value of theConsentIdfield returned in the consent creation response. stateis an OAuth parameter that lets you restore the state of the application after redirection. If provided, this value is returned in the redirect URI.nbfandexpshould contain the Unix timestamps before and after which the JWT is not valid.
For more details on these parameters, see the guide: Work with JSON Web Tokens.
Header:
{
"alg": "PS256",
"kid": "<insert kid>"
}
Body:
{
"response_type": "code id_token",
"client_id": "<insert client_id>",
"redirect_uri": "<insert redirect_uri>",
"aud": "https://oba-auth.revolut.com",
"scope": "payments",
"state": "<insert state>",
"nbf": <insert nbf>,
"exp": <insert exp>,
"claims": {
"id_token": {
"openbanking_intent_id": {
"value": "<insert ConsentId>"
}
}
}
}
4. Get consent from the user
Create an authorisation URL with the following parameters. Make sure they are URL-encoded.
| Parameter | Description | Required |
|---|---|---|
response_type | Always set to code id_token. | yes |
client_id | The client ID for your application. | yes |
redirect_uri | One of the redirect URIs that you defined when you created the application. Keep it static without variable parameters (https://example.com) and pass any return information or additional parameter via the state parameter in the JWT payload, not inside the redirect_uri. | yes |
scope | The scope that you are requesting, for example, accounts or payments. For payments, use the scope payments. | yes |
request | The encoded JWT generated in the previous step. | yes |
response_mode | If set to fragment, parameters are passed in the fragment section of the redirect URI. Otherwise, they are passed in the URI query. Passing parameters in fragment is considered to be more secure. | no |
A sample authorisation URL looks like this:
https://oba.revolut.com/ui/index.html?response_type=code%20id_token&scope=payments&redirect_uri=<insert redirect URL>&client_id=<insert client_id>&request=<insert JWT from step 3.>
Once you have redirected the user to the authorisation URL, they will need to provide their Revolut credentials and complete the payment authorisation.
After authorising the payment, the user will be redirected back to the redirect URL (redirect_uri) containing the authorisation code (code) as a URL parameter, as in the below example:
https://example.com/?code=oa_sand_sPoyVs-oMhyR36j5N-ZEVLfK9rQWPNssgIQqsOFZQ-c&id_token=<JWT id_token>&state=example_state
Use this authorisation code in the next step.
The code is valid only for two minutes.
:::
Payment consent status
The following table presents an overview of possible payment consent statuses and what they mean.
| Status | Meaning | Transitions |
|---|---|---|
AwaitingAuthorisation | Consent has been created, and PSU needs to be redirected using the authorisation URL to approve it. | |
Authorised | PSU approved; TPP may use the consent. | →Consumed, when the TPP attempts to use the consent (for example, to initiate payment). |
Consumed | Consent was used (attempt was made, regardless of the outcome). | → Final status, even if the attempted payment fails. |
Rejected | PSU declined or aborted the consent. | → Final status |
5. Exchange the authorisation code for an access token
Exchange the code parameter obtained when getting the consent for a new access token:
curl --key private.key --cert transport.pem \
--location --request POST 'https://oba-auth.revolut.com/token' \
--header 'Content-Type:application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=<your client_id>' \
--data-urlencode 'code=<insert code>'
Response
{
"access_token":"oa_prod_tP1Nofi1ixsRfBmVBtVPdIVN0J5x91imqmheQIWTS5s",
"token_type":"Bearer",
"expires_in":86376,
"id_token":"<JWT id_token>"
}
The access_token returned in this response will allow you to execute the payment.
This access token is valid only for 24 hours.
6. Initiate the domestic payment
Now you can initiate the domestic payment on the user's behalf. Make a call to the /domestic-payments endpoint to initiate the payment.
In the request body, use the same JSON content which was previously used in the consent request, and add the Data.ConsentId key with the ConsentId value which you received when creating the consent.
For this request, you must also use:
- The
x-jws-signatureheader, which you must calculate again with the new payload. - The access token which was obtained with the authorisation code.
curl --cert transport.pem --key private.key \
--location --request POST 'https://oba-auth.revolut.com/domestic-payments' \
--header 'x-fapi-financial-id: 001580000103UAvAAM' \
--header 'Content-Type: application/json' \
--header 'x-idempotency-key: 123' \
--header 'Authorization: Bearer <insert access_token from step 5.>' \
--header 'x-jws-signature: <insert JWS>' \
--data '
{
"Data": {
"ConsentId": "<insert ConsentId>"
"Initiation": {
"InstructionIdentification": "ID412",
"EndToEndIdentification": "E2E123",
"InstructedAmount": {
"Amount": "55.00",
"Currency": "GBP"
},
"CreditorAccount": {
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "11223321325698",
"Name": "Receiver Co."
},
"RemittanceInformation": {
"Unstructured": "Shipment fee"
}
}
},
"Risk": {
"PaymentContextCode": "EcommerceGoods",
"MerchantCategoryCode": "5967",
"MerchantCustomerIdentification": "1238808123123",
"DeliveryAddress": {
"AddressLine": ["7"],
"StreetName": "Apple Street",
"BuildingNumber": "1",
"PostCode": "E2 7AA",
"TownName": "London",
"Country": "UK"
}
}
}
Response:
{
"Data": {
"DomesticPaymentId": "dd817a13-3198-48ca-a5dc-f22b09ee82c0",
"Status": "Pending",
"StatusUpdateDateTime": "2020-11-20T08:54:30.115047Z",
"CreationDateTime": "2020-11-20T08:54:30.115047Z",
"ConsentId": "6686e444-c103-4077-8085-8e094200c425",
"Initiation": {
"CreditorAccount": {
"Name": "ReceiverCo.",
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "11223321325698"
},
"InstructedAmount": {
"Amount": "55.00",
"Currency": "GBP"
},
"RemittanceInformation": {
"Unstructured": "Shipment fee"
},
"EndToEndIdentification": "E2E123",
"InstructionIdentification": "ID412"
}
},
"Links": {
"Self": "https://oba-auth.revolut.com/domestic-payments/dd817a13-3198-48ca-a5dc-f22b09ee82c0"
},
"Meta": {
"TotalPages": 1
}
}
For more details about this call:
You have successfully initiated your first payment.
Save the DomesticPaymentId value from the response so that you can later check the status of the payment execution.
7. Check the status of the payment
After you create a single payment, it can go to either the Pending or AcceptedSettlementInProcess status.
Once the settlement on the debtor's account has been completed and the payment has been sent, it goes to the AcceptedSettlementCompleted status.
To check the current status of the payment that you created, make the following request and provide the payment ID.
Request
- You can use the previously generated access token with the grant type
client_credentialsand scopepaymentswhile it's still valid. After the token has expired, generate a new one in the same way. - Use the
DomesticPaymentIdvalue obtained when initiating the payment.
curl --cert transport.pem --key private.key \
--location 'https://oba-auth.revolut.com/domestic-payments/<DomesticPaymentId>' \
--header 'x-fapi-financial-id: 001580000103UAvAAM' \
--header 'Authorization: Bearer <access_token>'
Response
{
"Data": {
"DomesticPaymentId": "dd817a13-3198-48ca-a5dc-f22b09ee82c0",
"Status": "AcceptedSettlementCompleted",
"StatusUpdateDateTime": "2020-11-20T08:54:32.511691Z",
"CreationDateTime": "2020-11-20T08:54:30.115047Z",
"ConsentId": "6686e444-c103-4077-8085-8e094200c425",
"Initiation": {
"CreditorAccount": {
"Name": "ReceiverCo.",
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "11223321325698"
},
"InstructedAmount": {
"Amount": "55.00",
"Currency": "GBP"
},
"RemittanceInformation": {
"Unstructured": "Shipment fee"
},
"EndToEndIdentification": "E2E123",
"InstructionIdentification": "ID412"
}
},
"Links": {
"Self": "https://oba-auth.revolut.com/domestic-payments/dd817a13-3198-48ca-a5dc-f22b09ee82c0"
},
"Meta": {
"TotalPages": 1
}
}
Payment status
The following table present an overview of possible payment statuses and what they mean.
| Status | Meaning | Transitions |
|---|---|---|
Pending | Payment created/submitted; awaiting bank processing. |
|
AcceptedSettlementInProcess | Request accepted; payment is underway. |
|
AcceptedSettlementCompleted | Payment has been completed on our end and funds have been moved. | →Final status. Please note that if the recipient's bank rejects the payment later on, it will create a new transaction in the PSU's account. |
Rejected | Transaction has been rejected or failed validation. | →Final status |
For more details about this call:
What's next
- See the Open Banking API: Payment initiation for full details on all the supported types of payments.
- See Tutorials: Create your first draft payment for building your integration to our draft payments endpoints.