Integration Guide for Platform Users
This integration guide is for you, if you are a Platform that connects cross-border businesses, and is interested in facilitating money movement for exports of goods, services or software. If you are a business looking to get paid out for your exports, then please refer to this Integration Guide for Direct Users. Please note that, at this stage, we are supporting platforms incorporated in India or have an Indian entity that we can onboard. Additionally, we are facilitating money movement only for exports of goods, services and software happening from India, i.e. the Indian business entity is getting paid out.
In this guide, we will first walk you through how you can add and onboard your users/customers who will be referred to as connected users. Through these steps, we will also configure their experience (payout schedule, fees etc.). Post this, we will explain how you can create receivables on behalf of your connected users, receive funds from overseas businesses or buyers, and reconcile funds received with receivables. Lastly, we will look at how your connected users will get paid out with supporting documents that ensure compliance and understand your payouts as a platform facilitating the funds flow.
1. Create and activate a connected user
The first step in setting up your connected users to receive cross-border payments is to create and activate Xflow accounts on their behalf. Your connected users do not have access to these accounts; You will take actions on their behalf through these accounts. We will follow the steps below to cover the creation and activation of a connected user.
- Create an account for a connected user
- Add acceptance of Terms of Service (ToS)
- Add Know Your Business or KYB information (e.g. business legal name, business PAN card)
- Add Know Your Customers or KYC information (e.g. PAN cards for directors, and business representatives)
- Add a bank account for payouts to the connected user
- Configure the experience for the connected users
- Initiate account activation
You can perform the steps listed above over the API or use our Dashboard. Let's run through these steps.
Create an account for a connected user
You can create an account for a connected user as shown below by passing only a subset of the mandatory information required to activate the account. This will allow you to pass information about the connected user in a staged manner. Before initiating account activation, you will have to provide additional mandatory information. While some of this information is provided by using the update call on the account object, the remaining information is created separately and linked to the account object.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"business_details": {
"dba": "Marvelous Movies",
"email": "contact@marvelousmovies.com",
"legal_name": "Marvelous Movies India. Pvt. Ltd.",
"physical_address": {
"country": "IN"
},
"type": "company"
},
"type": "user"
}'
{
"address": "deactivated",
"business_details": {
"dba": "Marvelous Movies",
"email": "contact@marvelousmovies.com",
"ids": null,
"legal_name": "Marvelous Movies India. Pvt. Ltd.",
"physical_address": {
"city": null,
"country": "IN",
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"product_description": null,
"type": "company",
"website": null
},
"created": 1666077553,
"id": "account_F0A_1666077553446_41Hgq_000",
"link": null,
"livemode": true,
"logo_id": null,
"metadata": null,
"nickname": null,
"object": "account",
"parent_account_id": "account_F0A_1651077553223_98Hgq_000",
"supporting_documentation": null,
"status": "draft",
"tos_acceptance": null,
"type": "user"
}
With the call above:
- You have created an account of
type=user
. Your account on Xflow is oftype=platform
and is the parent account for your connected user’s account. See parameterparent_account_id
on the connected user's account - At this point, the connected user is in
status=draft
. Further in the guide, you will submit this account for activation and learn about state transitions on the account object - The default values for
account.fees
has are inherited by the connected user from the platform passthrough fees
Xflow does not support nor recommend cross-origin domain requests from the browser. If you make such requests from the browser, Xflow will return the CORS header 'Access-Control-Allow-Origin' missing
error code. If you encounter this error, you're likely calling an API endpoint that is not intended to be called by a web frontend. Or rather, it can't be safely called by your frontend, because it requires a secret API key, and you don't want to expose that key to clients. Please fix this issue by calling this API endpoint from your backend instead.
Add acceptance of Terms of Service (ToS)
Xflow needs the connected user to accept Xflow’s ToS that authorize Xflow as a 3rd party to collect and disburse payments on your behalf. Since Xflow does not directly interact with the connected users, you will provide a link to Xflow’s ToS under your own ToS for the connected users. Once the connected user accepts your ToS that includes Xflow’s ToS, you will capture and pass the following parameters from the acceptance event to Xflow: time
, ip
and user_agent
. This information is updated in tos_acceptance
hash on the account object. Xflow's ToS link that must be incorporated into your (i.e. the Platform) ToS. We recommend you do this by embedding the below language in the ToS you have with the connected user.
Your use of the [Platform name] processing service to receive export payments is subject to the Xflow Connected User Terms of Service (“Connected User ToS”) available at https://docs.xflowpay.com/connected-user-agreement/, which are hereby incorporated by reference. Please click on the link provided to read the Connected User ToS. In order to use the [Platform name] processing service, you hereby consent to and agree to be bound by the Connected User ToS. The Connected User ToS may be updated from time to time. You are expected to check the link provided above for any such updates. Your continued use of the [Platform name] processing services to receive export payments will be considered as your acceptance of the updated Connected User ToS.
Add KYB information
As part of the KYB information, you will provide Xflow with website
, product_description
, business_details.ids.business
and one of the following business identifiers on behalf of your connected users by updating the account object.
- GSTIN: You can pass this as a string in
business_details.ids.tax_gst
. Note that only forbusiness_details.type=partnership
, GSTIN is mandatory - Business PAN: You can pass this as a string in
business_details.ids.tax
. Note that forbusiness_details.type=sole_proprietor
orbusiness_details.type=individual
, you can pass the individual’s PAN number as a string - CIN /LLPIN: You can pass this as a string in
business_details.ids.business
You also have an option to provide these identifiers as an attachment or document instead of a keyed-in string. To do this, create a file object as below with the image or document of the identity document you want to submit. Xflow accepts jpeg
, pdf
or png
with size less than or equal to 10MB. The purpose of the document will vary by the type of document being uploaded.
- GST (GST certificate): Set the purpose to
tax_document
- Business PAN (Business PAN card): Set the purpose to
identity_document
. Note that forbusiness_details.type=sole_proprietor
orbusiness_details.type=individual
, you will upload the individual’s PAN card - CIN/LLPIN (Certificate of Incorporation): Set the purpose to
additional_verification
Once created, this file object must be updated in the supporting_documentation.id_document
on the connected user's account.
For business_details.type=partnership
, you will need to mandatorily provide us with the Partnership deed. This will be provided in supporting_documentation.id_document
with purpose of the file being set to identity_document
.
curl -L -X POST 'https://api.xflowpay.com/v1/files' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-F file=@"business_pan.pdf" \
-F payload='{
"purpose": "identity_document"
}'
{
"created": 1675153265,
"file_name": "business_pan.pdf",
"id": "file_F0A_1675153265850_IREzx_000",
"livemode": false,
"metadata": null,
"object": "file",
"purpose": "identity_document",
"size": 493117,
"type": "pdf",
"url": "https://api.xflowpay.com/v1/files/file_F0A_1675153265850_IREzx_000/contents"
}
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1666077553446_41Hgq_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"supporting_documentation": {
"id_document": "file_F0A_1675153265850_IREzx_000"
}
}'
At this stage, we would like to introduce the header Xflow-Account
. The header Xflow-Account
indicates the connected user's account for which the call is being made. You should pass this header with the connected user identifier when taking action in the context of that specific connected user.
Add KYC information
To complete the KYC section, you need to provide PAN details for individuals who hold the following position in the business entity being onboarded: Business Directors or Designated Partners, Business Representatives and Ultimate Beneficial Owners (UBO). This is done by creating a person object.
On the person object, you can provide PAN details by providing PAN number (string) in supporting_ids.tax
and name on the PAN card (string) in full_name
, or by simply uploading the image of the PAN card. To provide information as an image, start by creating a file object with the image of a PAN card as explained in the Add KYB section. Once you have created the file object, create the person object and link the appropriate PAN or Passport file identifier to this person object. This linking is done through supporting_documentation.id_document
on the person object. See below for an example.
curl -L -X POST 'https://api.xflowpay.com/v1/persons' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"relationship": {
"director": true
},
"supporting_documentation": {
"id_document": "file_F0A_1666078189630_vD3vd_000"
}
}'
{
"created": 1666078214,
"full_name": "Jean Krats",
"id": "person_F0A_1666078214977_3Du5D_000",
"livemode": true,
"object": "person",
"relationship": {
"director": true,
"owner": false,
"representative": false
},
"status": "unverified",
"supporting_documentation": {
"id_document": "file_F0A_1666078189630_vD3vd_000"
},
"supportings_ids": null
}
The role of the person is indicated through the relationship
hash on the person object. One person can have multiple relationships i.e. the same person can be a Business Director and Business Representative.
Add a bank account for the connected user
You can add an INR or USD EEFC (Exchange Earner's Foreign Currency) bank account for connected users. Your connected users will be paid out to this bank account. For adding an EEFC account, you will need to provide the bank statement as the supporting document that proves your connected user is the owner of the EEFC account (the business legal name of the connected user must be present on the supporting document that shows ownership of the account). For adding an INR bank account, you do not need to provide any supporting documents.
Payout bank accounts are modeled as an address object of category=user_payout
. If you are adding an EEFC account, create a file object first for uploading the supporting document (refer to Add KYB information), and then create the payout address as shown below. For INR accounts, skip the file object creation. Xflow-Ops will activate this payout address once the account has been submitted for activation.
curl -L -X POST 'https://api.xflowpay.com/v1/addresses' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d $'{
"bank_account": {
"global_wire": "HDFCINBBXXX",
"number": "102940182401"
},
"billing_details": {
"city": "Bangalore",
"country": "IN",
"line1": "No. 7 MG Road",
"postal_code": "560038",
"state": "Karnataka"
},
"category": "user_payout",
"currency": "USD",
"linked_id": "account_F0A_1666077553446_41Hgq_000",
"linked_object": "account",
"name": "Marvelous Movies India. Pvt. Ltd.",
"supporting_documentation": {
"id_document": "file_F0A_1666078209275_UpCMA_000"
},
"type": "bank_account"
}'
{
"bank_account": {
"domestic_credit": null,
"domestic_debit": null,
"domestic_fast_credit": null,
"domestic_wire": null,
"global_wire": "HDFCINBBXXX",
"iban": null,
"last4": "2401",
"number": "102940182401"
},
"billing_details": {
"city": "Bangalore",
"country": "IN",
"line1": "No. 7 MG Road",
"line2": null,
"postal_code": "560038",
"state": "Karnataka"
},
"category": "user_payout",
"created": 1666160723,
"currency": "USD",
"id": "address_f0A_1666160723235_fJSRH_000",
"is_reusable": false,
"linked_id": "account_F0A_1666077553446_41Hgq_000",
"linked_object": "account",
"livemode": true,
"metadata": null,
"name": "Marvelous Movies India. Pvt. Ltd.",
"object": "address",
"status": "deactivated",
"supporting_documentation": {
"id_document": "file_F0A_1666078209275_UpCMA_000"
},
"type": "bank_account",
"wallet": null,
}
Configure fees for the connected users
Xflow allows you to configure fees for connected users. All fees, whether for connected users, direct users or platforms, are modeled using the FeePlan
object. Let's take a closer look at the FeePlan
object and how fees can be configured for connected users.
When a connected user is created, Xflow creates a FeePlan
object of type=account_fees
for this connected user. Let's pull up a FeePlan
for a connected user. We will start by using the List endpoint.
curl -L -X GET 'https://api.xflowpay.com/v1/fee_plans?linked_id=account_F0A_1666077553446_41Hgq_000&type=account_fees&status=activated' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
{
"data": [
{
"created": 1666077553,
"deposit": [],
"id": "fee_plan_F0A_1666077553446_a8fQ1_000",
"linked_id": "account_F0A_1666077553446_41Hgq_000",
"linked_object": "account",
"livemode": true,
"metadata": null,
"object": "fee_plan",
"payout": [
{
"destination_currency": "*",
"fixed": "0.00",
"minimum": "0.00",
"source_currency": "*",
"variable": "1.00"
},
{
"destination_currency": "*",
"fixed": "100.00",
"minimum": "0.00",
"source_currency": "AUD",
"variable": "1.00"
},
{
"destination_currency": "*",
"fixed": "100.00",
"minimum": "0.00",
"source_currency": "EUR",
"variable": "1.00"
},
{
"destination_currency": "*",
"fixed": "100.00",
"minimum": "0.00",
"source_currency": "GBP",
"variable": "1.00"
}
],
"payout_fx": [
{
"destination_currency": "*",
"reference_rate": "mid_market",
"source_currency": "*",
"variable": "1.00"
},
{
"destination_currency": "INR",
"reference_rate": "mid_market",
"source_currency": "EUR",
"variable": "0.75"
}
],
"status": "activated",
"transfer_fx": [
{
"destination_currency": "*",
"reference_rate": "mid_market",
"source_currency": "*",
"variable": "1.00",
"variable_off_hours": "2.00"
},
{
"destination_currency": "GBP",
"reference_rate": "mid_market",
"source_currency": "EUR",
"variable": "0.75",
"variable_off_hours": "2.00"
}
],
"type": "account_fees",
"validity": {
"from": 1683072939,
"to": null
}
}
],
"has_next": false,
"object": "list"
}
The above FeePlan
object has the following characteristics:
- Fees are described for payouts, FX related to payouts and FX related to transfers
- Each of these above fees are described through a combination of
source_currency
anddestination_currency
- The character
*
is a wildcard and represents any currency supported by Xflow - Xflow selects a fee combination based on the most specific
source_currency
anddestination_currency
pair. If there are specific fee combinations with the same priority, then Xflow gives higher precedence tosource_currency
- Fixes fees are always specified in the receivable currency
The fee values for the default connected user FeePlan
(which Xflow creates) are equivalent to the values in the platform's FeePlan
object of type=account_fees_passthrough
. This behavior is to ensure that at a minimum the connected user fees are equivalent to the fees charged by Xflow (which in turn is represented by the platform's pass_through fees). The platform can always create a new FeePlan
object for a connected user once the connected has been created. The platform can do this by using the Create endpoint.
curl -L -X POST 'https://api.xflowpay.com/v1/fee_plans' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"linked_id": "account_F0A_1666077553446_41Hgq_000",
"linked_object": "account",
"payout": [
{
"destination_currency": "*",
"fixed": "0.00",
"minimum": "0.00",
"source_currency": "*",
"variable": "1.00"
},
{
"destination_currency": "*",
"fixed": "100.00",
"minimum": "0.00",
"source_currency": "AUD",
"variable": "1.00"
}
],
"payout_fx": [
{
"destination_currency": "*",
"reference_rate": "mid_market",
"source_currency": "*",
"variable": "1.00"
},
{
"destination_currency": "INR",
"reference_rate": "mid_market",
"source_currency": "EUR",
"variable": "0.75"
}
],
"transfer_fx": [
{
"destination_currency": "*",
"reference_rate": "mid_market",
"source_currency": "*",
"variable": "1.00",
"variable_off_hours": "2.00"
},
{
"destination_currency": "GBP",
"reference_rate": "mid_market",
"source_currency": "EUR",
"variable": "0.75",
"variable_off_hours": "2.00"
}
],
"type": "account_fees"
}'
Please note the following while creating of a FeePlan
for a connected user:
- The previous
FeePlan
is automatically expired andvalidity.to
field is populated - At a minimum,
source_currency=*
anddestination_currency=*
combinations are populated for payout, payout_fx and transfer_fx
Configure settings for the connected users
Xflow allows you to configure and personalize the experience of your connected users by making changes to the AccountSettings
object.
- Payout schedule: You can choose between
daily
,weekly
andmonthly
as the cadence at which your connected users get paid out. You can set this cadence throughpayouts.interval
on the connected user'sAccountSettings
object. Forweekly
cadence, you can set a specific day of week (e.g. get paid every Monday) throughpayouts.weekly_anchor
, and formonthly
cadence, you can choose a specific day of the month (e.g 5th day of every month) throughpayouts.monthly_anchor
. You may choose to allow your connected users to set these configurations, or just set a uniform configuration as per your needs. - Live FX: Live FX provides the option to enable instant payouts for your connected users. When the
live_fx
flag is set totrue
in your (Platform) AccountSettings, the configured payout schedule for all connected users will be ignored, and payouts will be initiated at the time of reconciliation, which means no aggregation of payouts will occur. If thelive_fx
flag is set tofalse
, payouts will adhere to the configured payout cadence for connected users. We will cover payouts and reconciliation in more detail later in this guide. - Default address availability: You can choose to change the default address availability for a connected user or the connected user's partner by changing the value for the
address.connected_user
or theaddress.connected_user_partner
(in the platform'sAccountSetting
) or theaddress.partner
(in the connected user'sAccountSetting
). Remember, you can also activate an address by using the Activate Address endpoint on an Account object.
Activate the connected user
Once you have completed all the steps above, you will now need to initiate the activation process by hitting the activation endpoint as shown below.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1666077553446_41Hgq_000/activate' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json'
Post this, the connected user status will change to verifying
. At this stage, Xflow will evaluate the information and activate (status changes to activated
) or ask for additional information over email (status changes to input_required
). The SLA here is 1 business day. You can subscribe to the event account.status.*
to receive these updates. Learn more about setting up webhooks.
Activate addresses for the connected user
If you require VBANs for the connected user, then initiate the activation process by hitting the address activation endpoint as shown below.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1666077553446_41Hgq_000/activate_address' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json'
Post this, the connected user address
parameter will change to requested
and then to processing
. Once the VBANs have been allocated, the address
parameter value will change to activated
. Xflow will also send out the account.address.activated
event once the VBANs are available.
2. Operate your connected user’s account
So now that you have created and activated an account of type=user
, your connected users can now start using your platform to receive payments from their overseas customers. Your connected user’s customers are called partners on Xflow. In this section, we will explain how to:
- Add a partner on behalf of the connected user
- Create a receivable against the partner
- Get a quote
Add a partner on behalf of the connected user
A partner is modeled as an account of type=partner
within Xflow. Use the create endpoint to create an account of type=partner
. You need to create the account on behalf of your user and so remember to pass the Xflow-Account
header populated with the user’s account identifier.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"business_details": {
"email": "contact@shoedog.com",
"legal_name": "Shoe Dog Inc.",
"physical_address": {
"city": "San Francisco",
"country": "US",
"line1": "185 Berry St",
"postal_code": "94107",
"state": "CA"
},
"type": "company"
},
"nickname": "Shoe Power",
"type": "partner"
}'
Unlike the connected user, activation of a partner is near instantaneous post creation and there is no separate call for activation required. However, given that this is still an asynchronous step, you can listen to the account.status.activated
event to determine when the partner account has been activated.
Activate addresses for the partner
If you require VBANs for the partner, then initiate the activation process by hitting the address activation endpoint as shown below.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1666184956871_JsBZ2_000/activate_address' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json'
Post this, the partner address
parameter will change to requested
and then to processing
. Once the VBANs have been allocated, the address
parameter value will change to activated
. Xflow will also send out the account.address.activated
event once the VBANs are available.
Create a receivable against the partner
Now that we have an activated partner, let’s create a receivable. A receivable is Xflow’s wrapper around a user’s invoice and is a precursor to accepting funds. In this case, create a receivable on behalf of your connected user to accept funds from an activated partner by using the below curl snippet:
curl -L -X POST 'https://api.xflowpay.com/v1/receivables' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"amount_maximum_reconcilable": "100.00",
"currency": "USD",
"invoice": {
"amount": "100.00",
"creation_date": "2023-01-02",
"currency": "USD",
"document": "file_F0A_1666079283600_ffoLd_000",
"due_date": "2023-03-02",
"reference_number": "INV-35/1"
},
"purpose_code": "P0104",
"transaction_type": "services"
}'
{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"amount_locked": "0.00",
"amount_maximum_reconcilable": "100.00",
"amount_reconcilable": "100.00",
"amount_reconciled": "0.00",
"amount_reconciled_not_settled": "0.00",
"amount_settled_payouts": "0.00",
"created": 1666079293,
"currency": "USD",
"deposit_ids_amount_locked": [],
"description": null,
"id": "receivable_f0A_1666261130393_wuH6k_000",
"invoice": {
"amount": "100.00",
"creation_date": "2023-01-02",
"currency": "USD",
"document": "file_F0A_1666079283600_ffoLd_000",
"due_date": "2023-03-02",
"reference_number": "Invoice FY 2022-23-038"
},
"livemode": true,
"metadata": null,
"object": "receivable",
"purpose_code": "P0104",
"purpose_code_description": "Receipts against export of goods not covered by the GR/PP/SOFTEX/EC copy of shipping bill etc.",
"status": "draft",
"system_message": [],
"transaction_type": "services"
}
Once you confirm a receivable, Xflow will verify and activate the receivable with an SLA of 2 hours. You can subscribe to the receivable.status.activated
event to know when a receivable has been activated. For some receivables, Xflow will require additional inputs and will move your receivable to an Input Required
status. You will be notified through webhook receivable.status.input_required
.
Get a quote
Xflow provides the ability for you to get indicative FX rates (both reference and the marked up user rate). Use the quotes object for this.
curl -L -X GET 'https://api.xflowpay.com/v1/quotes?buy.amount=-100.45&buy.currency=INR&sell.amount=&sell.currency=USD&type=payout_fx' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
3. Receive and reconcile funds
Receive funds from the partner at the partner-level
Once the partner has been activated, Xflow creates an address of category=xflow_receive
for the specific connected user-partner tuple (Note: you do not require an activated receivable to receive funds from a partner, an activated partner is sufficient) Let’s take a look at the address object in greater detail.
{
"bank_account": {
"domestic_credit": "322271627",
"domestic_debit": null,
"domestic_fast_credit": "322271627",
"domestic_wire": "21000021",
"global_wire": "CHASUS33",
"iban": null,
"last4": "9007",
"number": "0552439007"
},
"billing_details": {
"city": null,
"country": "US",
"line1": null,
"line2": null,
"postal_code": null,
"state": null,
},
"category": "xflow_receive",
"created": 1666160723,
"currency": "USD",
"id": "address_f0A_1666160723235_fJSRH_000",
"is_reusable": false,
"linked_id": "account_F0A_1666184956871_JsBZ2_000",
"linked_object": "account",
"livemode": true,
"metadata": null,
"name": "Marvelous Movies India. Pvt. Ltd.",
"object": "address",
"status": "activated",
"supporting_documentation": null,
"type": "bank_account",
"wallet": null,
}
An address object denotes a destination (for a store of value) within Xflow. Every address object has a unique identifier which is denoted by the id
parameter. Every address object stores funds of a specific currency which is denoted by the currency
parameter.
Xflow configures the address of category=xflow_receive
to be externally addressable by the connected user’s partners by providing several prefixes and a unique number
(also known as a Virtual Bank Account Number or VBAN) within the bank_account
hash:
- A
number
that denotes the unique bank account number into which funds can be transferred - Prefixes for different types of payment methods over bank rails. These prefixes are denoted by
domestic_credit
,domestic_debit
,domestic_fast_credit
,domestic_wire
andglobal_wire
So, if your connected user wants to receive funds from their partner using SWIFT, they can use the prefix global_wire
+ number
. On the other hand, if they want to receive funds using domestic wires (e.g. Fedwire in the United States), they can use the prefix domestic_wire
and number
. And of course, if the connected user wants to receive funds using local ACH, they can use the prefix domestic_credit
and number
.
Xflow currently supports only USD currency. Xflow will continue to add more currencies in the near-future. Please write-in to us at support@xflowpay.com if you'd like us to add support for a currency that we do not yet support.
Remember: category=xflow_receive
addresses are created such that they are unique to every connected user-partner tuple to facilitate easy reconciliation, and hence extra caution should be taken by you and the connected user while sharing bank transfer instructions with partners. If you share partner A’s bank transfer instructions with partner B, the funds received from partner A and B will start commingling creating reconciliation issues. If you do make an error and receive inwards from partner B in partner A’s address, reach out to Xflow-Ops at support@xflowpay.com for help.
When a partner sends funds, a deposit object is created (see below). You should subscribe to the deposit.status.completed
event to know when a connected user has received a deposit.
{
"amount": "20.00",
"created": 1666079759,
"currency": "USD",
"from": {
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"address_id": null
},
"id": "deposit_f0A_1666079759165_P3FtR_000",
"livemode": true,
"metadata": null,
"object": "deposit",
"payment_method": "domestic_credit",
"payment_method_details": null,
"reason_code": null,
"statement_descriptor": "Online Payment for Invoice FY 2022-23-038",
"status": "completed",
"to": {
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"address_id": "address_f0A_1666160723235_fJSRH_000"
}
}
The funds sent by the partner are reflected in your partner's pending balance. See the balance object below for the specific partner's account id.
{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"available": [],
"fee_advance": [],
"livemode": true,
"object": "balance",
"payout_processing": [],
"pending": [
{
"amount": "100.00",
"currency": "AED"
},
{
"amount": "900.00",
"currency": "AUD"
},
{
"amount": "200.00",
"currency": "CHF"
},
{
"amount": "10.00",
"currency": "CZK"
},
{
"amount": "10.00",
"currency": "DKK"
},
{
"amount": "100.00",
"currency": "EUR"
},
{
"amount": "200.00",
"currency": "GBP"
},
{
"amount": "120.00",
"currency": "HKD"
},
{
"amount": "100.00",
"currency": "NOK"
},
{
"amount": "600.00",
"currency": "PLN"
},
{
"amount": "100.00",
"currency": "SEK"
},
{
"amount": "500.00",
"currency": "SGD"
},
{
"amount": "200.00",
"currency": "USD"
}
],
"processing": []
}
Receive funds from the partner at the user-level
Xflow also provides the capability to receive funds from a partner into an address of category=xflow_receive
for the specific connected user. This address is created once the user has been activated. In this case, the funds sent by the partner are reflected in your user's pending balance. See the balance object below for the specific user's account id.
{
"account_id": "account_F0A_1666077553446_41Hgq_000",
"available": [
{
"amount": "500.00",
"currency": "AED"
},
{
"amount": "9000.00",
"currency": "AUD"
},
{
"amount": "2000.00",
"currency": "CHF"
},
{
"amount": "410.00",
"currency": "CZK"
},
{
"amount": "5610.00",
"currency": "DKK"
},
{
"amount": "100.00",
"currency": "EUR"
},
{
"amount": "200.00",
"currency": "GBP"
},
{
"amount": "120.00",
"currency": "HKD"
},
{
"amount": "500.00",
"currency": "NOK"
},
{
"amount": "600.00",
"currency": "PLN"
},
{
"amount": "100.00",
"currency": "SEK"
},
{
"amount": "500.00",
"currency": "SGD"
},
{
"amount": "3400.00",
"currency": "USD"
}
],
"fee_advance": [],
"livemode": true,
"object": "balance",
"payout_processing": [
{
"amount": "0.00",
"currency": "AED"
},
{
"amount": "0.00",
"currency": "AUD"
},
{
"amount": "0.00",
"currency": "CHF"
},
{
"amount": "0.00",
"currency": "CZK"
},
{
"amount": "0.00",
"currency": "DKK"
},
{
"amount": "0.00",
"currency": "EUR"
},
{
"amount": "0.00",
"currency": "GBP"
},
{
"amount": "120.00",
"currency": "HKD"
},
{
"amount": "500.00",
"currency": "NOK"
},
{
"amount": "650.00",
"currency": "PLN"
},
{
"amount": "100.00",
"currency": "SEK"
},
{
"amount": "500.00",
"currency": "SGD"
},
{
"amount": "300.00",
"currency": "USD"
}
],
"pending": [
{
"amount": "100.00",
"currency": "AED"
},
{
"amount": "900.00",
"currency": "AUD"
},
{
"amount": "200.00",
"currency": "CHF"
},
{
"amount": "10.00",
"currency": "CZK"
},
{
"amount": "10.00",
"currency": "DKK"
},
{
"amount": "100.00",
"currency": "EUR"
},
{
"amount": "200.00",
"currency": "GBP"
},
{
"amount": "120.00",
"currency": "HKD"
},
{
"amount": "100.00",
"currency": "NOK"
},
{
"amount": "600.00",
"currency": "PLN"
},
{
"amount": "100.00",
"currency": "SEK"
},
{
"amount": "500.00",
"currency": "SGD"
},
{
"amount": "200.00",
"currency": "USD"
}
],
"processing": [
{
"amount": "500.00",
"currency": "AED"
},
{
"amount": "200.00",
"currency": "AUD"
},
{
"amount": "200.00",
"currency": "CHF"
},
{
"amount": "10.00",
"currency": "CZK"
},
{
"amount": "10.00",
"currency": "DKK"
},
{
"amount": "100.00",
"currency": "EUR"
},
{
"amount": "200.00",
"currency": "GBP"
},
{
"amount": "120.00",
"currency": "HKD"
},
{
"amount": "100.00",
"currency": "NOK"
},
{
"amount": "600.00",
"currency": "PLN"
},
{
"amount": "100.00",
"currency": "SEK"
},
{
"amount": "500.00",
"currency": "SGD"
},
{
"amount": "300.00",
"currency": "USD"
}
]
}
Reconcile the receivable
The funds that have been transferred to the connected user’s address of category=xflow_receive
now need to be reconciled against a receivable before they become eligible for payout to the connected user. Reconciliation is easy and can be triggered using the reconcile endpoint.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1666261130393_wuH6k_000/reconcile' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"amount": "10.00"
}'
Once you reconcile a receivable, you will notice that:
- Pending balance is decremented by the amount you reconciled the receivable with. The funds from
balance.pending
at a partner or user-level move tobalance.available
at a user-level, and immediately after, these funds are moved tobalance.payout_processing
at a user-level once a payout has been initiated (covered in the next section). Once a payout has been settled, thebalance.payout_processing
at a user-level is decremented by an amount equal to what has been paid out - The field
amount_reconcilable
on the receivable is decremented by the amount you reconciled the receivable with. This will allow you to keep a track of receivable level pending amounts. See more fields on the Receivable object to understand complete Receivable level accounting.
4. Transfer funds (within Xflow)
Across partners of the same connected user
Xflow enables a platform to transfer funds across partner accounts belonging to the same connected user. This is possible via the creation of a transfer object. Please note that transfers may not complete instantaneously. The transfer.status.completed
is emitted by Xflow when a transfer reaches the terminal state completed
.
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1666079163481_A7fje_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_f0A_1646922461210_p5A23_000",
"currency": "USD"
},
"type": "internal_transfer"
}'
The above request transfers USD$ 123.40 across two partner accounts.
From a connected user to partner (or vice versa)
It's simple to send money between a connected user and partner. This is possible via the creation of a transfer object (which looks very similar to the request above). Remember, transfers are instantaneous and complete immediately.
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1666077553446_41Hgq_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_f0A_1646922461210_p5A23_000",
"currency": "USD"
},
"type": "internal_transfer"
}'
The above request transfers USD$ 123.40 from a connected user to a partner account.
Across partners of different users
Transferring funds across partners belonging to different connected users is a 3-step process (i) First, transfer funds partner 1 to connected user 1 (ii) Next, transfer funds from connected user 1 to platform (iii) Next, transfer funds from platform to connected user 2 and (iv) Finally, transfer funds from connected user 2 to partner 2. Below is an example of this 4-step process where we move USD$ 123.40 from Partner 1 -> Connected User 1 -> Platform -> Connected User 2 -> Partner 2 . Note the use of the different values of the transfer.type
parameter.
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID_1}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1666079163481_A7fje_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_F0A_1666077553446_41Hgq_000",
"currency": "USD"
},
"type": "internal_transfer"
}'
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID_1}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1666077553446_41Hgq_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_F0A_1651077553223_98Hgq_000",
"currency": "USD"
},
"type": "platform_credit"
}'
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID_2}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1651077553223_98Hgq_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_F0A_1651077598123_aB4r3_000",
"currency": "USD"
},
"type": "platform_debit"
}'
curl -L -X POST https://api.xflowpay.com/v1/transfers \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID_2}}"' \
-H 'Content-Type: application/json' \
-d '{
"from": {
"account_id": "account_F0A_1651077598123_aB4r3_000",
"amount": "123.40",
"currency": "USD"
},
"to": {
"account_id": "account_F0A_1651077599983_cp2qO_000",
"currency": "USD"
},
"type": "internal_transfer"
}'
5. Payouts to the connected user and platform
Payouts to the connected user
As explained above, once reconciliation is complete, Xflow will initiate a payout to the connected user. These payouts can be initiated (i) in INR or USD and (ii) instantly, daily, weekly or monthly. INR payouts will be settled to the connected user’s bank account via NEFT and USD payouts will be settled via a SWIFT wire (to a USD EEFC account). Here’s an example of a USD payout that has been settled to the connected user (Xflow will of course send a corresponding payout.status.settled
event webhook).
{
"amount": "4.50",
"arrival_date": 1675143082,
"automatic": true,
"created": 1666160776,
"currency": "USD",
"id": "payout_f0A_1666160776922_Q1hjW_000",
"livemode": true,
"metadata": null,
"object": "payout",
"payment_method": "global_wire",
"payment_method_details": null,
"payout_confirmation": null,
"statement_descriptor": "XFLOW PAYOUT F0A-1666160776922-J2uic-000",
"status": "settled",
"to": {
"account_id": "account_F0A_1666077553446_41Hgq_000",
"address_id": "address_f0A_1666160723235_fJSRH_000"
}
}
Once a payout has been initialized, Xflow will provide an ETA through the arrival_date
parameter which will indicate when Xflow expects the funds to reach your connected user’s bank account. The ETA takes into account the payout schedule set on the connected user’s account.
For every INR payout, Xflow will provide a payment advice that will capture details like the amount, purpose code associated with the payout, partner name and address and the ultimate beneficiary name and bank account details. The payment advice will be available 1 business day after the payout has been settled to the connected user. Xflow will make this payment advice available to you by updating the payout.payment_confirmation
parameter and sending you the payout.payment_confirmation.updated
event. We recommend that you make this payment advice available to your connected user within the platform experience. Please note that there is no payment advice available for payouts made to a USD EEFC account.
Your connected users will likely want to understand their payouts better, more specifically, which receivables funded this particular payout? To explain a payout, you will need to unpack the payout object. This is done by starting with payments of type = payout, and using linked_payments
to keep going upstream in the following sequence: payout
--> funds_debit
(gives Receivable ID) --> reconcile
(gives reconcile events). The initial unpacking has been provided below.
curl -L -X GET 'https://api.xflowpay.com/v1/payments?linked_id=payout_f0A_1666160776922_Q1hjW_000' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"'
{
"created": 1666160776,
"fee_plan_id": "fee_plan_F0A_1666077553446_a8sn4_000",
"from": {
"amount": "4.50",
"currency": "USD"
},
"id": "payment_f0A_1666160776922_DkBou_000",
"is_exchange_rate_applicable": false,
"linked_id": "payout_f0A_1666160776922_Q1hjW_000",
"linked_object": "payout",
"linked_payments": [
{
"account_id": "account_F0A_1666077553446_41Hgq_000",
"payment_id": "payment_f0A_1666160779999_ABCou_000",
"payment_type": "funds_debit"
}
],
"livemode": true,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "4.50",
"currency": "INR"
},
"type": "payout"
}
Once you have retrieved the payments object of type=payout
, you will need to look at the linked_payments
hash which represents upstream money movements. This hash will tell you three things about the upstream payments object (i) its unique identifier (ii) the account identifier with which it is associated and (iii) the type. Once you know the unique identifier, you can retrieve this upstream payment object by using a GET call (remember to use the appropriate account identifier in the header as required).
curl -L -X GET https://api.xflowpay.com/v1/payments/payment_f0A_1666160779999_ABCou_000 \
-H 'Xflow-Account: "{{Connected_User_Account_ID}}"' \
-H 'Authorization: Bearer sk_your_key'
If your connected user’s bank account can't receive a payout for any reason, the bank sends the funds back to Xflow. Consequently, the associated payout object will move to the failed
status. Xflow will ensure that the same amount of funds is returned in case of failure. You will need to reach out to support@xflowpay.com to retry a failed Payout. Also, subsequent payouts will be on hold
status for this particular connected user until you reach out to Xflow.
Payouts to the platform
You (the platform) will receive payouts from Xflow in INR or USD if you have added your markup over and above the Xflow pass-through fees. To understand how payouts to the platform work, let’s assume that you have added this markup. Every time your connected user gets a payout, Xflow will deposit your share of the fee (the markup over pass-through fees) into your pending balance. This will accumulate as per the payout schedule that you have set for yourself. You can retrieve your pending balance at any time to see your fees collected. One business day after your payout schedule, Xflow initiates a payout to your INR or USD EEFC bank account. To move these funds, Xflow creates a receivable on your behalf and attaches a payment statement as an invoice to this receivable. This receipt will list out total fees collected from your connected users, Xflow’s deduction (pass-through fees), and your net fees which are being paid out to you. You will also receive a payment advice for INR payouts.
If you would like to programmatically understand the composition of your payouts, you need to look at the transfer object (see below). Xflow maintains a single transfer object of type=platform_fee_payout
for a payout cycle during which there is a single payout. During the cycle, the transfer object is in processing
state. At the end of payout cycle, the transfer object is moved to completed state, and funds are moved to an Xflow-generated receivable and from there onwards to a payout object.
{
"created": 1645104826,
"description": "Monthly platform fees ",
"from": {
"account_id": null,
"amount": "5.50",
"currency": "USD"
},
"id": "transfer_f0A_1912224718366_laE3R_000",
"livemode": false,
"metadata": null,
"object": "transfer",
"status": "completed",
"to": {
"account_id": "account_F0A_1669103553223_AbC1e_000",
"amount": "5.50",
"currency": "USD"
},
"type": "platform_fee_payout",
"user_declaration": null
}
A transfer object is funded via fees collected by the platform from the connected users. A transfer object is debited for fees collected by Xflow from the platform. We will now explain these fee movements below to help you understand and keep a track of the ledger of payments.
- Retrieve the current transfer object using a list call with the filters
type=platform_fee_payout
andstatus=processing
. Remember there is only 1 transfer object per payout cycle. Now that you have the identifier of the transfer object, let’s proceed to unpack this object - The transfer is funded via payments of
type=transfer
which take funds away from the connected user payout and move them to the platform account to model platform fees for payout and FX markup. These underlying payments can be identified using thelinked_id
parameter pointing to the transfer object being examined. The payments oftype=transfer
themselves are linked upstream to connected user payments oftype=payout_fee
(for payout fees) ortype=payout
(for FX fees) - Xflow will deduct fees from the platform by creating payments of
type=payout_fee
andtype=fx_fee
. Both fees are linked to upstream payments oftype=transfer
and are associated with the transfer object being examined
Using the above, you could build your ledger of payments which can model a) payout fees collected from the connected user, b) FX markup collected from the connected user, c) payout fees deducted by Xflow, d) FX markup deducted by Xflow, and e) Platform net earnings. All of these components are brought together by a single transfer object.
Calculation of connected user's payout fee
Let's understand how the fees are calculated during a payout. Assume there is a payout of USD $1000, and the payout fee you set for this Connected User is a fixed USD $10, with a payout_fx fee of 1.00%. The fee calculation process by Xflow will proceed as follows:
- Deduction of Payout Fee: Xflow will first deduct the fixed payout fee of USD $10 from the USD $1000, resulting in a remaining balance of USD $990 for payout to the connected user.
- Application of Markup Rate: Next, Xflow will apply the markup rate you specified for this Connected User. For instance, if a 1.00% markup is applied to the mid-market rate, and the mid-market rate for the USD to INR corridor is 1 USD = 83.67 INR, the user will receive 1.00% less than the mid-market rate. This translates to an exchange marked up user rate of 82.8333 INR per 1 USD (i.e., 83.67 INR - 1.00% of 83.67).
Therefore, for a payout of USD $1000, the user will receive 82,004.967 INR after applying the fee and markup rate.
6. Platform pre-funding
Xflow supports the use case where a platform is charging its connected user lower fees than what the platform's own passthrough fees are. Xflow enables this by allowing a platform to pre-fund a special address of type xflow_fee_advance
. The platform can send funds into this address and if there is a shortfall in the fees owed to Xflow, then Xflow will auto-deduct the balance available in the balance.fee_advance
component of the platform. Please note that in the situation where there isn't sufficient balance available in the balance.fee_advance
component of the platform, Xflow will pause subsequent payouts to that specific connected user until sufficient funds have been provided in the address of type xflow_fee_advance
.
7. Testmode
You can use the testmode to simulate payments to test your integration. In testmode, You will be able to mimic livemode without actually moving real money. The advantage here is that if your integration works in testmode, you can have confidence that livemode will work once you decide to flip the switch. Please note that data from the testmode will not be carried over to the livemode. To make your testmode more productive, we provide additional testmode specific features:
- User activation: You can add dummy data for creating a connected user. There will be no verification checks for fields like business identifiers and your connected user will be activated instantaneously
- Partner activation: All partners created will be activated instantaneously
- Simulating a Deposit: You will be able to simulate payment from a partner to a connected user. You can do this via the API or from the Partner Details page within the context of a connected user. You will see a button Create a Deposit which will instantaneously add funds to the pending balance of the connected user
- Receivable activation: Receivables once confirmed will immediately auto-transition to
activated
status - Payouts: The initialized payout will auto-transition to
settled
status (i) immediately ifaccount.payouts.interval=daily
(ii) in 5 minutes ifaccount.payouts.interval=weekly
or (iii) in 15 minutes ifaccount.payouts.interval=monthly
- Payouts to the Platform: The transfer object which holds the platform fees will auto-transition to
completed
status (i) immediately ifaccount.payouts.interval=daily
(ii) in 5 minutes ifaccount.payouts.interval=weekly
or (iii) in 15 minutes ifaccount.payouts.interval=monthly
. Next, a platform receivable will be created by Xflow, reconciled from the transfer object funds and post this, be picked up by a payout 5 minutes after reconciliation. The initialized payout will auto-transition tosettled
status immediately - Platforms can pre-fund balance by creating a deposit in the
xflow_fee_advance
address to test the platform pre-funding use case - Payouts where fees are higher than the actual payout amount and transfers which are negative (due to fees being higher than the incoming funds) will be checked for incoming funds and updated every 5 minutes for 3 hours and subsequently once a week
8. API Versioning
Xflow supports API versioning for breaking changes! This means that you can safely continue to use whatever API version you are on (the API version itself is identified as the moniker yyyy-mm-dd) until you are ready to upgrade. The API docs you are looking at are versioned as well, you can identify which version you are on by looking at the dropdown on the top navigation bar. If you are looking to upgrade your API version, please see details here.
Integration Guide for Direct Users
You can use Xflow to reliably, compliantly and easily accept payments from your customers (called partners in Xflow) outside India and get paid out into your INR or EEFC bank account. Xflow supports payment acceptance in USD and models end-to-end money movement through a set of simple and easy-to-understand REST APIs.
This integration guide will show you how to create a partner, accept funds using your partner-specific bank accounts or PaymentLinks or partner Portal and then get paid out to your bank account. We also cover the basics of logging into the Xflow Dashboard, activating your account and creating livemode keys. We will finally show you how to use testmode, which is a simulation of the real-world using which you can test your integration.
1. Dashboard login
You will get an invite email from Xflow with the details of how to login to your account. Once you login you will reach the of Xflow Dashboard which will be in testmode. The home page of your Xflow dashboard will look something like this.
In testmode you can explore the Xflow Dashboard and the REST APIs and create test transactions to understand how Xflow works. You can find more details in the testmode section below. If you are looking to test the REST APIs, you can get your testmode key by clicking the Developers section on the Dashboard (see highlight in the image above).
You will also see an option to Activate your Account on the Dashboard (see highlight in the image above). By clicking on the Activate button and completing this section, you will get livemode access to Xflow capabilities. Using livemode, you can create live transactions and receive money from your partners. To activate your account you will need the following pieces of information - Business Information (like your Business Legal Name, Type of Business), Business Identifiers & Documents (like your CIN or LLP ID, Business PAN) and Bank Account Information (for you to receive money).
Once you provide the above information, Xflow will verify and then activate your account. Once your account is activated, you can create a livemode key in the Developers section on the Dashboard.
2. Create a partner
Before you start collecting funds, you need to define on Xflow who you want to collect money from, i.e. your customer. Xflow models your customer as an Account of type=partner
. You can create a partner on the Xflow Dashboard by using the Add Partner option or by using the API. To successfully create a partner, you will need to provide the email address, physical address, account type and a unique nickname. Let's take an example of a partner called Shoe Dog Inc., which is based out of the United States and wants to send funds to you via local payment rails like ACH credit, Fedwire or Real-Time Payments (RTP). The first step is to create an account for Shoe Dog Inc. using the request below.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"business_details": {
"email": "contact@shoedog.com",
"legal_name": "Shoe Dog Inc.",
"physical_address": {
"city": "San Francisco",
"country": "US",
"line1": "185 Berry St",
"postal_code": "94107",
"state": "CA"
},
"type": "company"
},
"nickname": "Shoe Power",
"type": "partner"
}'
Once a partner is created, you can visit the Partner Details page on the Dashboard from the Partner section, which looks something like this.
After partner creation, Xflow will verify and activate this partner. Once this is done, Xflow will create a Virtual Bank Account Numbers (VBAN) in USD which will be exclusively assigned to Shoe Dog Inc. VBANs are modeled as an address within Xflow. You can use these VBANs (or addresses) to receive payments from Shoe Dog Inc. created specifically to enable you to receive funds from your partners have category=xflow_receive
.
Since these VBANs are exclusive to Shoe Dog Inc., you can easily reconcile funds in these VBANs. Remember: VBANs are unique within and across partners, i.e. each currency for a partner has a unique VBAN. Hence, please ensure that the VBAN shared with a partner is the one for the currency in which they are making the payment. You can pull up VBAN details associated with a partner using the List API for the address object.
curl -L -X GET 'https://api.xflowpay.com/v1/addresses?account_id=account_F0A_1666184956871_JsBZ2_000&category=xflow_receive' \
-H 'Authorization: Bearer sk_your_key'
Here's what the response looks like.
{
"data": [
{
"bank_account": {
"domestic_credit": "028000024",
"domestic_debit": null,
"domestic_fast_credit": "028000024",
"domestic_wire": "021000021",
"global_wire": "CHASUS33XXX",
"iban": null,
"last4": "8492",
"number": "194248495038492"
},
"billing_details": null,
"category": "xflow_receive",
"created": 1666079069,
"currency": "USD",
"id": "address_f0A_1666184962045_5J7h9_000",
"is_reusable": false,
"linked_id": "account_F0A_1666184956871_JsBZ2_000",
"linked_object": "account",
"livemode": false,
"metadata": null,
"name": null,
"object": "address",
"status": "activated",
"type": "bank_account"
}
],
"has_next": false,
"object": "list"
}
The above response gives you all the VBANs associated with Shoe Dog Inc. through a list of address objects. Let's take a closer look at the bank_account
hash. This hash contains several prefixes and account numbers that can combine and provide to your partner using which they can transfer funds over desired payment rails. Take a look at the possible combinations of the prefix + account number parameters mapped to the different payment methods below.
Let's take the example of the USD VBAN to explain this further. Shoe Dog Inc. is a US-based entity and wants to send funds to you via Fedwire, which is a payment rail local to the US. All you have to do is provide the bank_account.domestic_wire
prefix and the bank_account.number
to Shoe Dog Inc. and they can use this information to transfer funds via Fedwire.
3. Receive funds
Now that you have created a partner, the next step is to create a receivable. A receivable models an intent expressed by you to accept funds from a partner. Typically, this intent is expressed by providing an underlying invoice for the desired inflow of funds. A receivable can be created via (i) the API (ii) the Xflow Dashboard using Create Receivable option or (iii) by sending an email to your Xflow assigned email. Let's walk through an example of creating the receivable through the API.
Every receivable must have an underlying invoice that provides proof of why funds are being accepted by the partner. The first step to creating a receivable on Xflow is to upload the underlying invoice. This is easy - use the files API to create a file on Xflow. Provide a digital copy of the invoice while creating this file.
curl -L -X POST 'https://api.xflowpay.com/v1/files' \
-H 'Authorization: Bearer sk_your_key' \
-F 'file=@"sample_invoice.pdf"' \
-F 'payload="{
\"purpose\": \"finance_document\"
}"'
You are now ready to create the receivable. In this example, let's create one for accepting $100 from Shoe Dog Inc. Remember to use the file_id
from the recently created file object which contains the underlying invoice as an input while creating this receivable.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"amount_maximum_reconcilable": "100.00",
"currency": "USD",
"invoice": {
"amount": "100.00",
"creation_date": "2022-08-02",
"currency": "USD",
"document": "file_F0A_1666079283600_ffoLd_000",
"due_date": "2022-10-02",
"reference_number": "INV-35/1"
},
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"purpose_code": "P0104",
"transaction_type": "services"
}'
Notice that receivable creation requires the presence of a valid export-related purpose code to facilitate the receiving of funds from outside of India. A full list of purpose codes that Xflow supports can be found here. The most commonly used purpose codes can be found here.
In response to the create action, Xflow will return a receivable object in draft
status.
{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"amount_locked": "0.00",
"amount_maximum_reconcilable": "100.00",
"amount_reconcilable": "100.00",
"amount_reconciled": "200.00",
"amount_reconciled_not_settled": "200.00",
"amount_settled_payouts": "0.00",
"created": 1666079293,
"currency": "USD",
"deposit_ids_amount_locked": [],
"description": "Shoe Dog Invoice Payment",
"id": "receivable_f0A_1666261130393_wuH6k_000",
"invoice": {
"amount": "100.00",
"creation_date": "2022-10-08",
"currency": "USD",
"document": "file_F0A_1666079283600_ffoLd_000",
"due_date": "2022-11-07",
"reference_number": "Invoice FY 2022-23-038"
},
"livemode": false,
"metadata": null,
"object": "receivable",
"purpose_code": "P0104",
"purpose_code_description": "Receipts against export of goods not covered by the GR/PP/SOFTEX/EC copy of shipping bill etc.",
"status": "activated",
"system_message": [],
"transaction_type": "services"
}
There are 6 amount fields on every receivable object.
amount_maximum_reconcilable
refers to the maximum funds that can be reconciled on this receivable. The upper limit for this value isinvoice.amount
but may be lower than theinvoice.amount
if you expect to receive funds lower than what is stated on the invoiceamount_reconcilable
is a calculated field and refers to the remaining amount that can be reconciled against this receivableamount_reconciled
is a calculated field and refers to the amount that has been reconciled against this receivable.amount_reconciled_not_settled
is a calculated field and refers to the amount that has not been paid out.amount_settled_payouts
is a calculated field and refers to the amount that has been paid out to you already (more about this in the Payouts section below)amount_locked
is a calculated field and refers to any amount that has been locked on this receivable. Locked amounts are not available for reconciliation. Amounts are locked when Xflow expects incoming funds against a receivable in the future, e.g. when an ACH debit is initiated via a PaymentLink.
Once you create a receivable, you can edit any of the parameters using the update endpoint. When the receivable updates are complete, you will need to finalize the receivable using the confirm endpoint.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1666261130393_wuH6k_000/confirm' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json'
The receivable will now move from draft
to verifying
status. At this point, Xflow will confirm that the receivable details match what is present in the underlying invoice. Once this is done, the receivable will move from verifying
to activated
status. The SLA for the receivable to move to the activated
state is 120 minutes during 8am-8pm IST on business days (if the receivable is confirmed outside business hours, the SLA for it to move to activated
status is by 10am IST the next business day). Note: Receivables moving to activated
status within the SLA is subject to complete & correct information being provided including a valid matching invoice. On the Dashboard, every receivable will have a receivable Details Page. It will look something like this.
Once activated, the receivable is now ready to receive funds from your partner Shoe Dog Inc. There are two approaches to receiving funds (i) via partner-initiated bank transfers and (ii) via PaymentLinks. Let's look at both approaches below.
Approach #1: Receive funds via partner-initiated bank transfers
Using this approach, the partner, Shoe Dog Inc, initiates a $50 bank transfer using Fedwire. Once the funds arrive within Xflow, a deposit object is created and your pending balance on the balance object is incremented by the same amount. You can retrieve the deposit object on the API or look at the partner Details page on the Dashboard (see below). You can go to the Partner Details page by clicking on any partner from the Partner section. You can see your overall pending balance from the balance page on the Dashboard.
{
"amount": "100.00",
"created": 1666079759,
"currency": "USD",
"from": {
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"address_id": "address_f0A_1666079747608_NDR0g_000"
},
"id": "deposit_f0A_1666079759165_P3FtR_000",
"livemode": false,
"metadata": null,
"object": "deposit",
"payment_method": "domestic_debit",
"reason_code": null,
"statement_descriptor": "Online Payment for Invoice FY 2022-23-038",
"status": "completed",
"to": {
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"address_id": "address_f0A_1666079171474_6jxOu_000"
}
}
At this stage, we would like to introduce you to the payments object. The payments object is a lower-level primitive which models the real-world money movement and is associated with a top-level object like a deposit. Thus every time there is money movement, Xflow will create an appropriate payment object to represent the money movement, and associate it with a top-level object. Consider the case of a deposit, which represents money moving into Xflow. This is modeled using a payment of type=funds_credit
. Let's retrieve the payment object associated with the deposit above.
curl -L -X GET 'https://api.xflowpay.com/v1/payments?linked_id=deposit_f0A_1666079759165_P3FtR_000' \
-H 'Authorization: Bearer sk_your_key'
The payment object associated with the deposit will look like this.
{
"created": 1666079759,
"fee_plan_id": "fee_plan_F0A_1666077553446_a8sn4_000",
"from": {
"amount": "100.00",
"currency": "USD"
},
"id": "payment_f0A_1666079759165_1QdMP_000",
"is_exchange_rate_applicable": false,
"linked_id": "deposit_f0A_1666079759165_P3FtR_000",
"linked_object": "deposit",
"linked_payments": [],
"livemode": false,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "100.00",
"currency": "USD"
},
"type": "funds_credit"
}
Now that you are familiar with the deposit and the underlying payment object, let's move on to reconciliation. At this point, the funds paid by the partner are not mapped to any receivable, therefore you will need to first reconcile the funds against an activated
receivable. Reconciling is simple - use the reconcile endpoint to carry out this action. This action will decrement your pending balance and increment the available balance on the balance object by the same amount. Remember that every time there is money movement, Xflow creates a payment object. Since the reconcile action moves funds from pending to available balance, Xflow creates a payment object of type=reconcile
. This Payment is also linked to a top-level object, in this case, a receivable. Note that, you could do multiple reconciles on a receivable, and each reconcile action will create a payment object of type=reconcile
.
curl -L -X POST 'https://api.xflowpay.com/v1/receivables/receivable_f0A_1666261130393_wuH6k_000/reconcile' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"amount": "100.00"
}'
Once the funds move to the available balance, they are ready to be paid out to you. While the actual payout particulars will be covered in the Payout section below, let's understand the money movement that precedes the actual payout. Every time your available balance is processed for a payout, money moves from the receivable object to the payout object. This is captured by the Payment of type=funds_debit
. The payment object of type=funds_debit
is linked to the payment object of type=reconcile
which represents the upstream money movement. This linkage is represented by providing the Payment identifier of the reconcile
object in the linked_payments
array of the funds_debit
object (the rule of thumb for linking is that the child object will contain the link to the parent object).
Approach #2: Receive funds via PaymentLinks
An alternative to receiving payments via bank transfer is to create PaymentLinks which allows your partners to pay for your invoices online using bank debits. This is a convenient and secure way to pay for your partners and is a far better experience for you as well. At present, PaymentLinks are available only for partners in the US and for receivables with currency=USD
. You can create PaymentLinks from i) the Receivable Details page on the Dashboard or ii) using the API. Let's look at an example and see how you can create a PaymentLinks object for your previously created invoice and it's associated receivable.
curl -L -X POST 'https://api.xflowpay.com/v1/payment_links' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json' \
-d '{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"receivable_ids": [
"receivable_f0A_1666261130393_wuH6k_000"
],
"type": "receivable"
}'
The response to the above call contains the PaymentLinks object in the link
parameter.
{
"account_id": "account_F0A_1666184956871_JsBZ2_000",
"created": 1666079629,
"expires_at": 1671263628,
"id": "pl_F0A_1666079628988_OkOfM_000",
"link": "https://checkout.xflowpay.com/checkout/pl_F0A_1666079628988_OkOfM_000",
"livemode": false,
"metadata": null,
"object": "payment_link",
"receivable_ids": [
"rec_f0A_1912224718366_laE3R_000"
],
"status": "activated",
"type": "receivable"
}
Once the PaymentLink is generated, you should share this link with your partner. Please note that there is no authentication required to access PaymentLinks, and hence you should not share these links with unauthorized individuals. A single PaymentLink can be associated with a single receivable, and the link expires within 60 days. While you can create multiple active PaymentLinks for a single receivable, it's advisable to keep a single link by deactivating stray links using the deactivate endpoint for easier link management.
Note: PaymentLinks is built in collaboration with Plaid which is a world-class fintech company that has simplified bank debit experience. Your partners will likely see Plaid branding on certain pages of our checkout experience.
Similar to bank transfers, when your partner pays via PaymentLink, a deposit object and an associated payment object of type=reconcile
is created. With PaymentLink, you get the additional advantage of not having to reconcile funds manually, since Xflow auto-reconciles the funds to the receivable attached to PaymentLink. Thus you will see that the available balance on the balance object is incremented by the deposit amount. Note that even for auto-reconcile, a payment object of type=reconcile
is created. Funds received via PaymentLinks are immediately processed for payout and should reach you within 4 business days after the partner completes the payment. On payout, your available balance is immediately decremented, and the pending balance is incremented by the same amount. This is covered in the Payout section. Similar to the bank transfer flow, a payment object of type=funds_debit
is created when the payout is processed.
Note: Instead of creating PaymentLinks for each receivable individually, you could also activate the Partner Portal, and share a single link with your partners. All your activated receivables automatically appear on the Partner Portal. Your partners can select multiple receivables and pay online using bank debits in one go. The post-payment workflow remains the same as PaymentLink. Partner Portal can be activated using the below API request or from the Partner Details page on the Dashboard.
curl -L -X POST 'https://api.xflowpay.com/v1/accounts/account_F0A_1666184956871_JsBZ2_000/activate_partner_portal' \
-H 'Authorization: Bearer sk_your_key' \
-H 'Content-Type: application/json'
4. Get paid out
Xflow currently supports payouts to India in INR and USD. By default, payouts are enabled to an INR bank account. If you want to be paid out in other currencies, contact support@xflowpay.com. Remember, you will require a valid EEFC account if you want Xflow to pay you out in a foreign currency. You won't receive your first payout until 7–14 days after processing your first successful payment. Subsequent payouts will reach you in T+1 business days.
INR payouts will be made from Xflow's partner bank to your bank account via NEFT. Xflow will provide you with a valid Payment Advice which will capture details like the purpose code, partner name and address and the ultimate beneficiary name and bank account details. The Payment Advice will help your bank initiate the process for closing the EDPMS entry if applicable. The narration on the NEFT will also contain a similar set of details to what is present in the Payment Advice.
Foreign currency payouts will require you to work with your bank such that they complete the final transfer to your EEFC bank account and further to your INR bank account should you so desire.
Every payout on Xflow is captured as a payout object, and the underlying money movement from Xflow to your bank account is captured by a Payment of type=payout
which is linked to at least 1 Payment of type=funds_debit
. Here's what the payout for the $100 receivable against Shoe Dog Inc. looks like. Once the payout has been processed, Xflow will provide an ETA through the arrival_date
parameter which will indicate when Xflow expects the funds to reach your bank account.
{
"amount": "8210.90",
"arrival_date": null,
"automatic": true,
"created": 1666160776,
"currency": "INR",
"id": "payout_f0A_1666160776922_Q1hjW_000",
"livemode": true,
"metadata": {
"internal_reference": "ABC-1234"
},
"object": "payout",
"payment_method": "domestic_credit",
"payout_confirmation": null,
"statement_descriptor": "XFLOW PAYOUT F0A-1666160776922-J2uic-000",
"status": "processing",
"to": {
"account_id": "account_F0A_1666077553446_41Hgq_000",
"address_id": "address_f0A_1666160723235_fJSRH_000"
}
}
On the Dashboard, you can go to the Payouts section and view all your payouts. You can click on any payout, and go to the Payout Details page. The Payout Details Page looks something like this.
Every payout object can be broken down into the source receivables and amounts, i.e. how a payout object has been funded. This is done by starting with Payment of type=payout
, and using linked_payments
to keep going upstream in the following sequence - payout
> funds_debit
(gives receivable identifier) > reconcile
(gives reconcile events). The initial unpacking has been provided below.
Retrieve the Payment of type=payout
associated with the payout.
curl -L -X GET 'https://api.xflowpay.com/v1/payments?linked_id=payout_f0A_1666160776922_Q1hjW_000' \
-H 'Authorization: Bearer sk_your_key'
The response for the above call is as follows.
{
"created": 1666160776,
"from": {
"amount": "100.00",
"currency": "USD"
},
"id": "payment_f0A_1666160776922_DkBou_000",
"is_exchange_rate_applicable": true,
"linked_id": "payout_f0A_1666160776922_Q1hjW_000",
"linked_object": "payout",
"linked_payments": [
{
"account_id": "account_f0A_2396160776922_Q1hjW_000",
"payment_id": "payment_f0A_1666127191112_z6ou8_000",
"payment_type": "funds_debit"
}
],
"livemode": true,
"object": "payment",
"payout_eligible_at": null,
"to": {
"amount": "8210.90",
"currency": "INR"
},
"type": "payout"
}
Once you have retrieved the payment object of type=payout
associated with your payout, you will need to look at linked_payments
which represents upstream money movements. You can simply retrieve the linked payment object with a GET call as follows.
curl -L -X GET https://api.xflowpay.com/v1/payments/pay_f0A_1646924718366_ljD9R_000 \
-H 'Authorization: Bearer sk_your_key'
The payment object will be of type=funds_debit
which models money movement from receivable to the payout object. Thus the linked Payment for a funds_debit
will be a receivable and linked_id
will be the source receivable identifier. We now know the source receivable and the amount which makes up a part of the payout that you received.
If your bank account can't receive a payout for any reason, your bank sends the funds back to Xflow. Consequently, the associated payout object will move to failed
status. Xflow will ensure that the exact amount of funds sent out will be what is returned to you in case of failure. At this time, reach out to the Xflow Support team by emailing support@xflowpay.com so that we can help you to retry this payout. Until you do so, the remaining payouts will be on hold
status and will not be sent out of Xflow.
5. Testmode
You can use testmode to simulate payments to test your integration. In testmode, You will be able to mimic livemode without actually moving real money. The advantage here is that if your integration works in testmode, you can have confidence that livemode will work once you decide to flip the switch.
In testmode you can simulate both the offline payments flow & the online payments flow.
Offline payments flow
In this flow, your partner will deposit money into the bank account provided by you. You can use the steps described above in the integration guide to test this flow. Some testmode particulars to keep in mind:
a. You can create a deposit (testmode only feature) by clicking on Create a Deposit button on the Partner Details page. This step is required to simulate incoming funds from a partner.
b. Receivables once confirmed will auto-transition to activated
status in 5 minutes
c. Receivables once reconciled will be picked up by a payout 5 minutes after reconciliation
d. The initialized payout will auto-transition to settled
status (i) immediately if account.payouts.interval=daily
(ii) in 5 minutes if account.payouts.interval=weekly
or (iii) in 15 minutes if account.payouts.interval=monthly
Online payments flow
In this flow, your partner pays via the PaymentLink that you share with them. Steps to create and use the PaymentLinks have been described in the integration guide. Once you have generated a PaymentLink, within testmode, you can simulate your partner's online payment experience, and observe the changes to your Dashboard or key objects like receivables, balances, and payouts.
a. On the Receivable Details page, locate the PaymentLinks widget, and click on Create button. On the next screen, click on the Preview Link button to open the PaymentLinks and view your partner's experience.
b. Here you will see an option to Add a bank account. Once you click this - you will see a selection menu of 2 bank accounts 1) Success and 2) Failure. Choose the account marked as Success and click on the Pay button
c. You will receive an email saying your partner has made a payment. On the partner Detail page, you will see an entry under the Payment history with type=Bank Debit
, and on the Receivable Details page, you will see an entry in the Transaction history table of type=Auto Reconciled
.
d. The failure bank account has been provided for simulating the failed scenario. When you choose this type of bank account, none of the above changes take place. We inform your partner about the payment failure and ask them to retry.
Note: The data from testmode does not carry into livemode. Once you get access to livemode, you can always flip to testmode by using the testmode specific key in case you need to test a feature.
Footnotes
- The most commonly used export-related purpose codes are provided in the table below:
Webhooks
Xflow uses webhooks to notify the platform or the direct user when an event happens in your account. You can register up to 16 webhook endpoints with Xflow. All platform integrations should establish a webhook endpoint to listen for the platform and connected user events. There are two different types of webhooks:
- Platform webhooks (
platform=true
on the WebhookEndpoint object) are for activity on your account (for example, most requests made using your API keys and without authenticating as another Xflow account) - Non-Platform webhooks (
platform=false
on the WebhookEndpoint object) are for activity on any connected user account
Please note that livemode events are received only on livemode
webhook endpoints and testmode events are received only on testmode
webhook endpoints (for both platform and connected user accounts).
Here are some things to keep in mind as you setup webhooks on Xflow.
1. Event types
Your webhook endpoints should be configured to receive only the types of events required by your integration. This can be done by specifying the event types to listen to in the enabled_events
parameter while creating or updating a webhook endpoint. Listening for extra events (or all events) will put undue strain on your server and is not recommended.
2. Retry logic
In livemode
and testmode
, Xflow attempts to deliver your webhooks for up to 1 day with an exponential back off. If your endpoint has been disabled or deleted when we attempt a retry, future retries of that event will be prevented. However, if you disable and then re-enable a webhook endpoint before we’re able to retry, you should still expect to see future retry attempts. Webhooks cannot be manually retried.
3. Disable logic
In livemode
and testmode
, Xflow will disable a misconfigured endpoint if that endpoint has not responded with a 2xx
HTTP status code for multiple days in a row. You can always reactivate a disabled endpoint.
4. Handle duplicate events
Webhook endpoints might occasionally receive the same event more than once. We advise you to guard against duplicated event receipts by making your event processing idempotent. One way of doing this is logging the events you’ve processed (Xflow passes back the event identifier in the webhook), and then not processing already-logged events.
5. Order of events
Xflow does not guarantee delivery of events in the order in which they are generated. Your endpoint shouldn’t expect delivery of these events in this order and should handle this accordingly.
6. CSRF protection
If you’re using Rails, Django, or another web framework, your site might automatically check that every POST request contains a CSRF token. This is an important security feature that helps protect you and your users from cross-site request forgery attempts. However, this security measure might also prevent your site from processing legitimate events. If so, you might need to exempt the webhooks route from CSRF protection.
7. Source IP Addresses
In case your webhook receiving endpoint is behind a firewall or NAT, you may need to allow traffic from the below IP addresses. This is the full list of IP addresses that webhooks may originate from:
54.216.8.72
54.173.54.49
52.215.16.239
52.55.123.25
52.6.93.106
63.33.109.123
44.228.126.217
50.112.21.217
52.24.126.164
54.148.139.208
8. Receive events with an HTTPS server
To receive events, your server must be correctly configured to support HTTPS with a valid server certificate. Xflow webhooks currently support TLS v1.2 and v1.3.
9. Verify events are sent from Xflow
Each webhook call includes three headers with additional information that are used for verification:
Webhook-Id
the unique message identifier for the webhook message. This identifier is unique across all messages, but will be the same when the same webhook is being resent (e.g. due to a previous failure)Webhook-Timestamp
timestamp in seconds since epochWebhook-Signature
the base64 encoded list of signatures (space delimited)
Constructing the signed content
The content to sign is composed by concatenating the id, timestamp and payload, separated by the full-stop character (.). In code, it will look something like:
String toSign = String.format("%s.%s.%s", Webhook-Id, Webhook-Timestamp, body);
Where body
is the raw body of the request. The signature is sensitive to any changes, so even a small change in the body will cause the signature to be completely different. This means that you should not change the body
in any way before verifying. One of the common errors we've seen is users using a prettified JSON request of the body
- please do not do this!
Determining the expected signature
Xflow uses an HMAC with SHA-256 to sign its webhooks. So to calculate the expected signature, you should HMAC the toSign
from above using the webhook secret. For example, given the secret webhook_secret_f0A_1646921929908_lO60W_000
you will want to use webhook_secret_f0A_1646921929908_lO60W_000
as the key.
The generated signature should match one of the ones sent in the Webhook-Signature
header. The Webhook-Signature
header is composed of a list of space delimited signatures and their corresponding version identifiers. The signature list is most commonly of length one (though there could be any number of signatures). Make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.
Verify timestamp
As mentioned above, Xflow also sends the timestamp of the attempt in the Webhook-Timestamp
header. You should compare this timestamp against your system timestamp and make sure it's within your tolerance in order to prevent timestamp attacks.