Send recurring remittances with a fixed receive 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 recipient receives a fixed amount.
This approach is particularly useful for remittance app scenarios where:
- The sender and the recipient each transact in different currencies
- The sender wants the recipient to receive a fixed amount, denominated in the recipient’s local currency, on a recurring basis
- The sender is willing to cover any differences in the exchange rate
Scenario
Section titled “Scenario”Imagine someone in the US wants to send money to a family member in Mexico. They want their family member to receive an exact amount in Mexican pesos (MXN) each month.
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 recipient will receive exactly $4,000 MXN each month for three months.
The amount debited from the sender’s payment account 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:
- Recipient receives: $4,000 MXN each month for three months
- Exchange rate: A static rate of $1 USD for every $20 MXN
- Sender pays: $200 USD each month
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 Create a Quote
- POST Grant Continuation Request
- POST Create an Outgoing Payment
1. Get wallet address information
Section titled “1. Get wallet address information”When the sender initiates a 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 interactive outgoing payment grant
Section titled “2. Request an interactive outgoing payment grant”Use the sender’s authServer information received in the previous step 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 wants the recipient to receive a fixed amount of $4,000 MXN each month, the request must have a limits object containing a receiveAmount and an interval.
receiveAmount- The maximum amount that the recipient can receive 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', receiveAmount: { assetCode: 'MXN', assetScale: 2, value: '400000', } } } ] }, 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 the recipient to receive $4,000 MXN 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 any number of outgoing payments within the defined limit 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
3. Start interaction with the sender
Section titled “3. 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.
4. Finish interaction with the sender
Section titled “4. 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.
5. Request a grant continuation
Section titled “5. 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", "receiveAmount": { "assetCode": "MXN", "assetScale": 2, "value": "400000" } } } ] }, "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, 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 }}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.
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:26:55.52Z", "methods": [ { "type": "ilp", "ilpAddress": "...", "sharedSecret": "..." } ]}8. Request a quote grant
Section titled “8. Request a quote grant”Use the sender’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 a quote resource be created on the sender’s wallet.
const senderQuoteGrant = await client.grant.request( { url: senderWalletAddress.authServer }, { access_token: { access: [ { type: 'quote', actions: ['create'] } ] } })Example response
The following shows an example response from the sender’s wallet provider.
{ "access_token": { "value": "...", // access token value for quote grant "manage": "https://auth.cloudninebank.example.com/token/{...}", // management uri for access token "access": [ { "type": "quote", "actions": ["create"] } ] }, "continue": { "access_token": { "value": "..." // access token for continuing the request }, "uri": "https://auth.cloudninebank.example.com/continue/{...}" // continuation request uri }}9. Request the creation of a quote resource
Section titled “9. Request the creation of a quote resource”Use the access token received in the previous step to call the POST Create Quote API.
This call requests that a quote resource be created on the sender’s wallet account. The request must contain the receiver, which is the recipient’s incoming payment id, along with the receiveAmount, which is the exact amount the sender wants the recipient to receive.
The receiveAmount specifies that the recipient will receive exactly $4,000 MXN.
const senderQuote = await client.quote.create( { url: senderWalletAddress.resourceServer, accessToken: senderQuoteGrant.access_token.value }, { method: 'ilp', walletAddress: senderWalletAddress.id, receiver: recipientIncomingPayment.id, receiveAmount: { value: '400000', assetCode: 'MXN', assetScale: 2 } })The response returns a receiveAmount, a debitAmount, and other required information.
debitAmount- The amount the sender must pay (in USD in our example) after currency conversion.receiveAmount- The amount the recipient will actually receive (exactly $4,000 MXN in our example).
Example response
The following shows an example response from the sender’s wallet provider.
{ "id": "https://cloudninebank.example.com/quotes/{...}", // url identifying the quote "walletAddress": "https://cloudninebank.example.com/sender", "receiver": "https://happylifebank.example.com/incoming-payments/{...}", // url of the incoming payment the quote is created for "debitAmount": { "value": "20000", "assetCode": "USD", "assetScale": 2 }, "receiveAmount": { "value": "400000", // Recipient receives $4,000 MXN "assetCode": "MXN", "assetScale": 2 }, "method": "ilp", "createdAt": "2025-10-03T23:28:51.50Z", "expiresAt": "2025-10-03T23:48:51.50Z"}10. Request the creation of an outgoing payment resource
Section titled “10. Request the creation of an outgoing payment resource”Use the access token returned in the outgoing payment grant continuation response (Step 5) to call the POST Create Outgoing Payment API.
const senderOutgoingPayment = await client.outgoingPayment.create( { url: senderWalletAddress.resourceServer, accessToken: senderOutgoingPaymentGrant.access_token.value }, { walletAddress: senderWalletAddress.id, quoteId: senderQuote.id, })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 "assetCode": "MXN", "assetScale": 2 }, "sentAmount": { "value": "0", "assetCode": "USD", "assetScale": 2 }, "createdAt": "2025-10-03T23:29:03.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:
- An incoming payment resource (step 7)
- A quote resource (step 9)
- An outgoing payment resource (step 10)
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.