Send recurring remittances with a fixed debit amount
A remittance payment is a transfer of money from one person to another, typically across borders or long distances, often involving currency conversion and fees. In this guide, you will learn how to set up a recurring payment from a sender in which the sender pays a fixed amount. The recipient’s received amount may vary with exchange rate fluctuations.
This approach is particularly useful for remittance app scenarios where:
- The sender and the recipient each transact in different currencies
- The sender wants to send a fixed amount in their own currency on a recurring schedule
- The sender and the recipient are comfortable that the delivered amount may vary with exchange rate fluctuations
Scenario
Section titled “Scenario”Imagine someone in the US wants to send money to a family member in Mexico. They want to send exactly $200 US Dollars (USD) from their account, regardless of how much their family member actually receives after currency conversion.
For this guide, you’ll assume the role of a developer building a remittance app. This guide explains how to set up a recurring payment in US dollars, where the sender pays exactly $200 USD each month for three months.
The amount delivered to the recipient can vary each month depending on changes to the exchange rate. For simplicity, this guide assumes a static exchange rate of $1 USD for every $20 MXN.
Example transaction details:
- Sender pays: $200 USD each month (fixed)
- Exchange rate: A static rate of $1 USD for every $20 MXN
- Recipient receives: $4,000 each month for three months (actual may vary)
The three parties involved in this scenario are the:
- Developer: you, the person building the remittance app
- Sender: the person using your app to send money in USD
- Recipient: the person receiving the money in MXN
Endpoints
Section titled “Endpoints”- GET Get Wallet Address
- POST Grant Request
- POST Create Incoming Payment
- POST Grant Continuation Request
- POST Create an Outgoing Payment
1. Get wallet address information
Section titled “1. Get wallet address information”When the sender sets up a recurring payment through your app, you must get wallet address information for both the sender and the recipient.
Let’s assume the sender saved their wallet address to their profile settings in your app. Let’s also assume the sender entered the recipient’s wallet address into your app’s payment form.
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 responses
The following example shows a response from the sender’s wallet provider.
{ "id": "https://cloudninebank.example.com/sender", "assetCode": "USD", "assetScale": 2, "authServer": "https://auth.cloudninebank.example.com/", "resourceServer": "https://cloudninebank.example.com/op"}The following example shows a response from the recipient’s wallet provider.
{ "id": "https://happylifebank.example.com/recipient", "assetCode": "MXN", "assetScale": 2, "authServer": "https://auth.happylifebank.example.com/", "resourceServer": "https://happylifebank.example.com/op"}2. Request an incoming payment grant
Section titled “2. Request an incoming payment grant”Use the recipient’s authServer details, received in Step 1, to call the POST Grant Request API.
This call obtains an access token that allows your app 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: ['create'], }, ], }, },);Example response
The following shows an example response from the recipient’s wallet provider.
{ "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"] } ] }, "continue": { "access_token": { "value": "..." // access token for continuing the request }, "uri": "https://auth.happylifebank.example.com/continue/{...}" // continuation request uri }}3. Request the creation of an incoming payment resource
Section titled “3. 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.
const recipientIncomingPayment = await client.incomingPayment.create( { url: recipientWalletAddress.resourceServer, accessToken: recipientIncomingPaymentGrant.access_token.value }, { walletAddress: recipientWalletAddress.id, },)Example response
The following shows an example response from the recipient’s wallet provider.
{ "id": "https://happylifebank.example.com/incoming-payments/{...}", "walletAddress": "https://happylifebank.example.com/recipient", "receivedAmount": { "value": "0", "assetCode": "MXN", "assetScale": 2 }, "completed": false, "createdAt": "2025-10-03T23:24:55.52Z", "methods": [ { "type": "ilp", "ilpAddress": "...", "sharedSecret": "..." } ]}4. Request an interactive outgoing payment grant
Section titled “4. Request an interactive outgoing payment grant”Use the sender’s authServer information received in Step 1 to call the POST Grant Request API.
This call obtains an access token that allows your app to request outgoing payment resources be created on the sender’s wallet account.
Because the sender will pay a fixed amount of $200 USD per month, the request must have a limits object containing a debitAmount and an interval.
debitAmount- The maximum amount that can be debited from the sender per interval. When the next interval begins, the value resets.interval- The time interval under which the grant is valid.
const pendingSenderOutgoingPaymentGrant = await client.grant.request( { url: senderWalletAddress.authServer }, { access_token: { access: [ { identifier: senderWalletAddress.id, type: 'outgoing-payment', actions: ['create'], limits: { interval: 'R3/2025-10-03T23:25:00Z/P1M', debitAmount: { assetCode: 'USD', assetScale: 2, value: '20000', // $200.00 USD per interval } } } ] }, interact: { start: ['redirect'], finish: { method: 'redirect', uri: 'https://myapp.example.com/finish/{...}', // where to redirect your user after they've completed the interaction nonce: NONCE } } })Example response
The following shows an example response from the sender’s wallet provider.
{ "interact": { "redirect": "https://auth.interledger-test.dev/{...}", // uri to redirect your 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-10-03T23:25:00Z/P1M. Remember that the sender wants to send $200 USD a month for three months. The interval breaks down like this:
R3/is the number of repetitions - three2025-10-03is the start date of the repeating interval - 03 October 2025T23:25:00Z/is the start time of the repeating interval - 11:25 PM UTCP1Mis 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 the sender to make outgoing payments from:
- 11:25 PM UTC on 03 October 2025 through 11:24 PM UTC on 03 November 2025
- 11:25 PM UTC on 03 November 2025 through 11:24 PM UTC on 03 December 2025
- 11:25 PM UTC on 03 December 2025 through 11:24 PM UTC on 03 January 2026
5. Start interaction with the sender
Section titled “5. Start interaction with the sender”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.
6. Finish interaction with the sender
Section titled “6. Finish interaction with the sender”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.uriprovided 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
finishcall, and an interaction reference as query parameters to the URI.
7. Request a grant continuation
Section titled “7. Request a grant continuation”In our example, we’re assuming the IdP your user (the sender) interacted with has a user interface. When the interaction completes, your user returns to your app. Now your app 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 app to continue the outgoing payment grant request.
Issue the request to the continue.uri provided in the initial outgoing payment grant response.
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
The following shows an example response from the sender’s wallet provider.
{ "access_token": { "value": "...", // final access token required before creating outgoing payments "manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token "access": [ { "type": "outgoing-payment", "actions": ["create"], "identifier": "https://cloudninebank.example.com/sender", "limits": { "interval": "R3/2025-10-03T23:25:00Z/P1M", "debitAmount": { "assetCode": "USD", "assetScale": 2, "value": "20000" } } } ] }, "continue": { "access_token": { "value": "..." // access token for continuing the request }, "uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri }}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 outgoing payment grant continuation response (Step 7) to call the POST Create Outgoing Payment API. Create this payment by referencing the new incomingPayment URL and the fixed debitAmount.
const senderOutgoingPayment = await client.outgoingPayment.create( { url: senderWalletAddress.resourceServer, accessToken: senderOutgoingPaymentGrant.access_token.value }, { walletAddress: senderWalletAddress.id, incomingPayment: recipientIncomingPayment.id, debitAmount: { assetCode: 'USD', assetScale: 2, value: '20000' }, })Example response
The following shows an example response from the sender’s wallet provider.
{ "id": "https://cloudninebank.example.com/outgoing-payments/{...}", // url of 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": "20000", // The amount to debit from the sender's account "assetCode": "USD", "assetScale": 2 }, "receiveAmount": { "value": "400000", // Recipient to receive $4,000 MXN (actual may vary by rate) "assetCode": "MXN", "assetScale": 2 }, "sentAmount": { "value": "0", "assetCode": "USD", "assetScale": 2 }, "createdAt": "2025-10-03T23:27:45.41Z"}The first payment is now set up. At the next interval (one month from now), repeat the following steps to request the creation of:
Use the access token associated with each resource’s grant in the requests. You don’t need to request new grants because the original grants should still be valid.