Make recurring payments
The Open Payments APIs facilitate multiple use cases for recurring payments to and from Open Payments-enabled wallets. Paying a fee for a monthly subscription service is one example.
This guide provides the steps your Open Payments-enabled client will take to set up a $50 USD payment from a user that recurs once a month for three months. There’s a few different ways to set up the payment. In this guide, you’ll begin by getting an outgoing payment grant, then request the creation of an incoming payment resource and an outgoing payment resource at each interval.
Endpoints
Section titled “Endpoints”- GET Get Wallet Address
- POST Grant Request
- POST Create an Incoming Payment
- POST Create an Outgoing Payment
- POST Rotate an Access Token
1. Get wallet address information
Section titled “1. Get wallet address information”To set up a recurring payment, the client must get the wallet address information for both the sender and recipient.
Let’s assume the sender’s wallet address is already saved in your client. The sender is essentially your client’s user. Let’s also assume the sender has entered the recipient’s wallet address into a payment form in your client application.
Call the GET Get Wallet Address API for each address.
const senderWalletAddress = await client.walletAddress.get({ url: 'https://cloudninebank.example.com/sender'})const recipientWalletAddress = await client.walletAddress.get({ url: 'https://happylifebank.example.com/recipient'})
Example response
The following shows an example response from the recipient’s wallet provider. A similar response will be returned from the sender’s wallet provider.
{ "id": "https://happylifebank.example.com/recipient", "assetCode": "USD", "assetScale": 2, "authServer": "https://auth.happylifebank.example.com/", "resourceServer": "https://happylifebank.example.com/op"}
2. Request an interactive outgoing payment grant
Section titled “2. Request an interactive outgoing payment grant”Use the sender wallet’s authServer
information received in the previous step to call the POST Grant Request API.
This call obtains an access token that allows your client to request outgoing payment resources be created on the sender’s wallet account.
Include the following in the request:
limits
objectdebitAmount
- The maximum amount that the user will pay per interval (for example, per month). When the next interval begins, the value resets.interval
- The time interval under which the grant is valid.
Remember that your user, the sender, wants to make payments of $50 a month for three months. The amount resets each month and any unspent portions don’t roll over.
const pendingSenderOutgoingPaymentGrant = await client.grant.request( { url: senderWalletAddress.authServer, }, { access_token: { access: [ { identifier: senderWalletAddress.id, type: 'outgoing-payment', actions: ['read', 'create'], limits: { interval: 'R3/2025-03-12T23:25:00Z/P1M', debitAmount: { assetCode: 'USD', assetScale: 2, value: '5000', }, }, }, ], }, interact: { start: ['redirect'], finish: { method: 'redirect', uri: 'https://paymentplatform.example/finish/{...}', // where to redirect the user after they've completed the interaction nonce: NONCE, }, }, },);
Example response
{ "interact": { "redirect": "https://auth.interledger-test.dev/{...}", // uri to redirect the user to, to begin interaction "finish": "..." // unique key to secure the callback }, "continue": { "access_token": { "value": "..." // access token for continuing the outgoing payment grant request }, "uri": "https://auth.interledger-test.dev/continue/{...}", // uri for continuing the outgoing payment grant request "wait": 30 }}
About the interval
Section titled “About the interval”The interval used in this guide is R3/2025-03-12T23:25:00Z/P1M
. Remember that your user wants to send payments of $50 USD a month for three months. The interval breaks down like this:
R3/
is the number of repetitions - three2025-03-12
is the start date of the repeating interval - 12 March 2025T23:25:00Z/
is the start time of the repeating interval - 11:25 PM UTCP1M
is the period between each interval - one month. Used withR3
, you have a grant that’s valid once a month for three months.
Altogether, this grant will allow your user to send $50 USD three times, from:
- 11:25 PM UTC on 12 March 2025 through 11:24 PM UTC on 12 April 2025
- 11:25 PM UTC on 12 April 2025 through 11:24 PM UTC on 12 May 2025
- 11:25 PM UTC on 12 May 2025 through 11:24 PM UTC on 12 June 2025
3. Start interaction with the user
Section titled “3. Start interaction with the user”Once the client receives the authorization server’s response, it must send the user to the interact.redirect
URI contained in the response. This starts the interaction flow.
The response also includes a continue
object, which is essential for managing the interaction and obtaining explicit user consent for outgoing payment grants. The continue
object contains an access token and a URI that the client will use to finalize the grant request after the user has completed their interaction with the identity provider (IdP). This ensures that the client can securely obtain the necessary permissions to proceed with the payment process.
4. Finish interaction with the user
Section titled “4. Finish interaction with the user”The user interacts with the authorization server through the server’s interface and approves or denies the grant.
Provided the user approves the grant, the authorization server:
- Sends the user to the
finish.uri
provided in the interactive outgoing payment grant request. The means by which the server sends the user to the URI is out of scope, but common options include redirecting the user from a web page and launching the system browser with the target URI. - Secures the redirect by adding a unique hash, allowing your client to validate the
finish
call, and an interaction reference as query parameters to the URI.
5. Request a grant continuation
Section titled “5. Request a grant continuation”In this guide, we’re assuming the IdP the user interacted with has a user interface. When the interaction completes, the user is returned to your client. Now your client can make a continuation request for the outgoing payment grant.
Call the POST Grant Continuation Request API. This call obtains an access token that allows your client to continue the outgoing payment request.
Issue the request to the continue.uri
provided in the initial outgoing payment grant response (Step 2).
Include the interact_ref
returned in the redirect URI’s query parameters.
const senderOutgoingPaymentGrant = await client.grant.continue( { url: pendingSenderOutgoingPaymentGrant.continue.uri, accessToken: pendingSenderOutgoingPaymentGrant.continue.access_token.value }, { interact_ref: interactRef })
Example response
{ "access_token": { "value": "...", // final access token required before creating an outgoing payment "manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token "access": [ { "type": "outgoing-payment", "actions": ["create", "read"], "identifier": "https://cloudninebank.example.com/sender", "limits:" { "interval": "R3/2025-03-12T23:25:00Z/P1M" "debitAmount": { "assetCode": "USD", "assetScale": 2, "value": "5000", } } } ] }, "continue": { "access_token": { "value": "..." // access token for continuing the request }, "uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri }}
6. Request an incoming payment grant
Section titled “6. Request an incoming payment grant”Use the recipient’s authServer
details, returned in Step 1, to call the POST Grant Request API.
This call obtains an access token that allows your client to request an incoming payment resource be created on the recipient’s wallet account.
const recipientIncomingPaymentGrant = await client.grant.request( { url: recipientWalletAddress.authServer }, { access_token: { access: [ { type: "incoming-payment", actions: ["read", "create"], }, ], }, },);
Example response
{ "access_token": { "value": "...", // access token value for incoming payment grant "manage": "https://auth.happylifebank.example.com/token/{...}", // management uri for access token "access": [ { "type": "incoming-payment", "actions": ["create", "read"] } ] }, "continue": { "access_token": { "value": "..." // access token for continuing the request }, "uri": "https://auth.happylifebank.example.com/continue/{...}" // continuation request uri }}
7. Request the creation of an incoming payment resource
Section titled “7. Request the creation of an incoming payment resource”Use the access token returned in the previous response to call the POST Create Incoming Payment API.
This call requests an incoming payment resource be created on the recipient’s wallet account.
The value
is the maximum allowable amount that can be paid into this resource. Since the monthly payment is for $50 USD, the value
is 5000
.
const recipientIncomingPayment = await client.incomingPayment.create( { url: recipientWalletAddress.resourceServer, accessToken: recipientIncomingPaymentGrant.access_token.value }, { walletAddress: recipientWalletAddress.id, incomingAmount: { value: '5000', assetCode: 'USD', assetScale: 2 }, },)
Example response
{ "id": "https://happylifebank.example.com/incoming-payments/{...}", "walletAddress": "https://happylifebank.example.com/recipient", "incomingAmount": { "value": "5000", "assetCode": "USD", "assetScale": 2 }, "receivedAmount": { "value": "0", "assetCode": "USD", "assetScale": 2 }, "completed": false, "createdAt": "2025-03-12T23:20:50.52Z", "methods": [ { "type": "ilp", "ilpAddress": "...", "sharedSecret": "..." } ]}
8. Request the creation of an outgoing payment resource
Section titled “8. Request the creation of an outgoing payment resource”Use the access token returned in the grant continuation response (Step 5) to call the POST Create Outgoing Payment API.
const senderOutgoingPaymentToRecipient = await client.outgoingPayment.create( { url: senderWalletAddress.resourceServer, accessToken: senderOutgoingPaymentGrant.access_token.value }, { walletAddress: senderWalletAddress.id, incomingPayment: 'https://happylifebank.example/com/incoming-payments/{...}', },)
Example response
{ "id": "https://cloudninebank.example.com/outgoing-payments/{...}", // url identifying the outgoing payment "walletAddress": "https://cloudninebank.example.com/sender", "receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment being paid "debitAmount": { "value": "5000", "assetCode": "USD", "assetScale": 2 }, "receiveAmount": { "value": "5000", "assetCode": "USD", "assetScale": 2 }, "sentAmount": { "value": "0", "assetCode": "USD", "assetScale": 2 }, "createdAt": "2025-03-12T23:27:54.52Z"}
The first of the three recurring payments is now set up. At the next interval (for example, one month from now), repeat Steps 6 - 8. You don’t need to request new outgoing or incoming payment grants because the original grants are still valid.