Get Sandbox Access

Paybase Developer Centre

OverviewAPI GuidesGetting StartedRecipesGig Economy PlatformsSharing Economy PlatformsMarketplacesBlockchain BusinessesEscrowSandboxRolesRulesDue DiligenceCustomersAccountsBank AccountsCardsTransactionsIntroductionInboundGetting money into the systemTransaction ReferenceAccount ReferenceInternalOutboundEscrowSplit PaymentsRefundsStrong Customer Authentication3D Secure AuthenticationIntroductionCreate a cardCreate a transactionDocument UploadStatementsWebhooksErrorsPQLAPI ReferenceAccountCreate an accountRetrieve an accountTransition account statusList all accountsAnnotate an accountDelete annotation from an accountTag an accountDelete tag from an accountBank AccountCreate a bank accountRetrieve a bank accountUpdate a bank accountTransition bank account statusList all bank accountsAnnotate a bank accountDelete annotation from a bank accountTag a bank accountDelete tag from a bank accountCardCreate a cardRetrieve a cardUpdate a cardTransition card statusList all cardsAnnotate a cardDelete annotation from a cardTag a cardDelete tag from a cardCardholderCreate a cardholderRetrieve a cardholderUpdate a cardholderTransition cardholder statusList all cardholdersAnnotate a cardholderDelete annotation from a cardholderTag a cardholderDelete tag from a cardholderCreate an authentication tokenCheckCreate a checkCustomerIndividual CustomerCreate a customerRetrieve a customerUpdate a customerSole TraderCreate a customerRetrieve a customerUpdate a customerOrganisationCreate a CustomerRetrieve a CustomerUpdate a CustomerIncorporated BusinessCreate a customerRetrieve a customerUpdate a customerBusiness PersonAdd a business personRetrieve a business personUpdate a business personDelete a business personRetrieve a customerTransition state of a customerList all customersAnnotate a customerDelete annotation from a customerTag a customerRemove tag from a customerCreate an authentication tokenTouch a customerDocumentCreate a documentRetrieve a documentList Document TypesReferenceRetrieve a referenceStatementRetrieve a statementStatusRetrieve API statusTransactionCreate inbound transactionCreate internal transactionCreate outbound transactionRetrieve a transactionTransition transaction statusList all transactionsAnnotate a transactionDelete annotation from a transactionTag a transactionDelete tag from a transaction
API version: ba085a6

Escrow

Escrow

Escrow is the process of holding funds with a regulated entity and a trusted third party until specific conditions have been met. Paybase is the regulated entity and the trusted third-party, but you have full control of customer communication, branding and commercial model.

Our clients in this space are either stand-alone escrow providers, trusted brands who can leverage their trust capital or platform businesses who recognise that escrow is a great way to improve trust within their community and reduce network leakage.

You can use Paybase to provide a compliant Escrow solution where funds from buyers are held in Escrow until they are ready to be released to the seller. You can decide the conditions and timing required to be fulfilled in order to release funds to the seller.

Onboarding sellers

Seller refers to any individuals or businesses that are selling goods on your platform. Given that you control the entire experience of the sellers on your platform, and Paybase never interacts directly with them, you will need to build flows to collect the necessary information required for performing Due Diligence on the sellers. You will also need to display a link to the Paybase Terms that the workers will need to accept as part of your onboarding flow. Once you collect this information, you will be able to create the seller as a Customer on Paybase, and attach an Account and Bank Account to them.

Before you can create a Customer, you will need to know the following:

  • Whether the worker is an Individual, Sole Trader or Incorporated Business
  • Seller’s Role and Target CDD Level associated with it. The Target CDD Level of the Role will determine the minimum information required to complete due diligence on the worker for them to be able to start receiving payments. This is set-up by Paybase when your Sandbox account is created and can be found on Console

To create a seller who is an individual, use the Create an Individual Customer request with the roleSlug as determined above. You will also need to provide the following details about the lister:

  • First Name
  • Last Name
  • Email
  • Phone Number
  • Full Residential Address
  • Date of Birth
  • Version of Paybase Terms accepted and the time of acceptance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.createIndividualCustomer({
  roleSlug: "seller",
  profile: {
    firstName: "John",
    lastName: "Doe",
    dob: "1986-04-29T00:00:00.000Z",
    email: "john.doe@email.com",
    phoneNumber: "+442078137221",
    residentialAddress: {
      postalCode: "SE6 9YU",
      countryISO: "GB",
      houseNameNumber: "7",
      street: "Brick Lane",
      townCity: "London"
    }
  },
  terms: {
    acceptedAt: "2019-08-16T18:29:02.013Z",
    revision: "2.1"
  }
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/customers/individual",
  data = json.dumps({
    "roleSlug": "seller",
    "profile": {
      "firstName": "John",
      "lastName": "Doe",
      "dob": "1986-04-29T00:00:00.000Z",
      "email": "john.doe@email.com",
      "phoneNumber": "+442078137221",
      "residentialAddress": {
        "postalCode": "SE6 9YU",
        "countryISO": "GB",
        "houseNameNumber": "7",
        "street": "Brick Lane",
        "townCity": "London"
      }
    },
    "terms": {
      "acceptedAt": "2019-08-16T18:29:02.013Z",
      "revision": "2.1"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/customers/individual",
  [
    "body" => "{
      \"roleSlug\": \"seller\",
      \"profile\": {
        \"firstName\": \"John\",
        \"lastName\": \"Doe\",
        \"dob\": \"1986-04-29T00:00:00.000Z\",
        \"email\": \"john.doe@email.com\",
        \"phoneNumber\": \"+442078137221\",
        \"residentialAddress\": {
          \"postalCode\": \"SE6 9YU\",
          \"countryISO\": \"GB\",
          \"houseNameNumber\": \"7\",
          \"street\": \"Brick Lane\",
          \"townCity\": \"London\"
        }
      },
      \"terms\": {
        \"acceptedAt\": \"2019-08-16T18:29:02.013Z\",
        \"revision\": \"2.1\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/customers/individual"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \
  -d '{
    "roleSlug": "seller",
    "profile": {
      "firstName": "John",
      "lastName": "Doe",
      "dob": "1986-04-29T00:00:00.000Z",
      "email": "john.doe@email.com",
      "phoneNumber": "+442078137221",
      "residentialAddress": {
        "postalCode": "SE6 9YU",
        "countryISO": "GB",
        "houseNameNumber": "7",
        "street": "Brick Lane",
        "townCity": "London"
      }
    },
    "terms": {
      "acceptedAt": "2019-08-16T18:29:02.013Z",
      "revision": "2.1"
    }
  }'

The response to a successful API call will be the full Customer object including the id, stateId, cddLevel and targetCDDLevel. Store the id in your database as you will need to pass it for any future requests associated with this Customer. The stateId will be returned as ENABLED. However, note that a Customer can only start transacting when their cddLevel equals their targetCDDLevel. The cddLevel will initially be returned as ZERO because the due diligence checks, whilst automated, are performed asynchronously. However, if automated checks pass, then the cddLevel of the Customer will be updated to the targetCDDLevel shortly after creation. You can monitor asynchronous events using webhooks.

The Customers page provides further guidance on creating other types of Customers such as Incorporated Businesses and Sole Traders.

Creating accounts for sellers

With a seller created, you now need to create an Account for them. An Account on the Paybase platform is an entity that holds balances for Customers as e-money under the regulatory licence of Paybase. This is where your sellers will be paid before they can manually or automatically withdraw their balance to their bank account.

When creating accounts and transactions for your customers, you will need to generate and use a separate customer authentication token instead of your normal API token.

With the customer authentication token, use the Create Account API to create a new Account and associate it with the seller by providing the previously saved Customer id in the ownerId attribute.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c"
});

const result = await client.createAccount({
  ownerId: "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
  currencyISO: "GBP"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/accounts",
  data = json.dumps({
    "ownerId": "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
    "currencyISO": "GBP"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/accounts",
  [
    "body" => "{
      \"ownerId\": \"customer/82c6fb26-8027-454a-8451-ae37c0f9d72c\",
      \"currencyISO\": \"GBP\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/accounts"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "ownerId": "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
    "currencyISO": "GBP"
  }'

The response to the API call will be the full Account object. You should store the account id as this will be required when you create transactions and interact with the account.

Adding bank accounts for sellers

Finally, you should add the seller’s bank details for making withdrawals by adding a bank account for them. Provide the seller’s id in the ownerId attribute. You will need to use a customer authentication token for adding bank accounts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "customer/0315c31b-b316-486f-bce1-90325f49286a"
});

const result = await client.createBankAccount({
  ownerId: "customer/0315c31b-b316-486f-bce1-90325f49286a",
  accountNumber: "41406760",
  sortCode: "309457",
  countryISO: "GB",
  currencyISO: "GBP",
  billingAddress: {
    postalCode: "SE6 9YU",
    countryISO: "GB",
    houseNameNumber: "7",
    street: "Brick Lane",
    townCity: "London"
  }
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/bank-accounts",
  data = json.dumps({
    "ownerId": "customer/0315c31b-b316-486f-bce1-90325f49286a",
    "accountNumber": "41406760",
    "sortCode": "309457",
    "countryISO": "GB",
    "currencyISO": "GBP",
    "billingAddress": {
      "postalCode": "SE6 9YU",
      "countryISO": "GB",
      "houseNameNumber": "7",
      "street": "Brick Lane",
      "townCity": "London"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/bank-accounts",
  [
    "body" => "{
      \"ownerId\": \"customer/0315c31b-b316-486f-bce1-90325f49286a\",
      \"accountNumber\": \"41406760\",
      \"sortCode\": \"309457\",
      \"countryISO\": \"GB\",
      \"currencyISO\": \"GBP\",
      \"billingAddress\": {
        \"postalCode\": \"SE6 9YU\",
        \"countryISO\": \"GB\",
        \"houseNameNumber\": \"7\",
        \"street\": \"Brick Lane\",
        \"townCity\": \"London\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/bank-accounts"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "ownerId": "customer/0315c31b-b316-486f-bce1-90325f49286a",
    "accountNumber": "41406760",
    "sortCode": "309457",
    "countryISO": "GB",
    "currencyISO": "GBP",
    "billingAddress": {
      "postalCode": "SE6 9YU",
      "countryISO": "GB",
      "houseNameNumber": "7",
      "street": "Brick Lane",
      "townCity": "London"
    }
  }'

The response to the API call will be the full bank account object. As the first bank account created for the seller, it will automatically be set as their preferred payment instrument and any automated withdrawals will be made to this bank account. If a customer has multiple bank accounts, any one of them can be set as preferred from the update bank account API.

To reduce friction during onboarding, you do not necessarily need to create bank accounts for sellers as part of your initial onboarding flow, and can also do this when they withdraw funds for the first time. However, if you plan to automate withdrawals you will need to create bank accounts at the onboarding stage.

Onboarding buyers

Buyers refers to the participants on your platform who will purchase goods from sellers created above. These will again be individuals or businesses depending on the type of platform you operate.

Similar to onboarding sellers, you will need to build flows to collect the necessary information required to allow buyers to make payments on your platform.

Buyers will also be created on Paybase as Customers with the relevant role. Since placing funds in Escrow will require buyers to pay in by bank transfer, Paybase will perform due diligence on the buyers and therefore the Target CDD Level of the role will usually be two.

You will also need to display a link to the Paybase Terms that the buyers will need to accept as part of your onboarding flow.

Before you can create a buyer, you will need to know the following:

  • Whether the buyer is an Individual, Sole Trader or Incorporated Business
  • Buyer’s Role and Target CDD Level associated with it.

To create a buyer who is an individual, use the Create an Individual Customer request with the roleSlug as determined above. You will also need to provide the following details about the buyer:

  • First Name
  • Last Name
  • Email
  • Phone Number
  • Full Residential Address
  • Date of Birth
  • Version of Paybase Terms accepted and the time of acceptance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.createIndividualCustomer({
  roleSlug: "buyer",
  profile: {
    firstName: "John",
    lastName: "Doe",
    dob: "1986-04-29T00:00:00.000Z",
    email: "john.doe@email.com",
    phoneNumber: "+442078137221",
    residentialAddress: {
      postalCode: "SE6 9YU",
      countryISO: "GB",
      houseNameNumber: "7",
      street: "Brick Lane",
      townCity: "London"
    }
  },
  terms: {
    acceptedAt: "2019-08-16T18:29:02.013Z",
    revision: "2.1"
  }
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/customers/individual",
  data = json.dumps({
    "roleSlug": "buyer",
    "profile": {
      "firstName": "John",
      "lastName": "Doe",
      "dob": "1986-04-29T00:00:00.000Z",
      "email": "john.doe@email.com",
      "phoneNumber": "+442078137221",
      "residentialAddress": {
        "postalCode": "SE6 9YU",
        "countryISO": "GB",
        "houseNameNumber": "7",
        "street": "Brick Lane",
        "townCity": "London"
      }
    },
    "terms": {
      "acceptedAt": "2019-08-16T18:29:02.013Z",
      "revision": "2.1"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/customers/individual",
  [
    "body" => "{
      \"roleSlug\": \"buyer\",
      \"profile\": {
        \"firstName\": \"John\",
        \"lastName\": \"Doe\",
        \"dob\": \"1986-04-29T00:00:00.000Z\",
        \"email\": \"john.doe@email.com\",
        \"phoneNumber\": \"+442078137221\",
        \"residentialAddress\": {
          \"postalCode\": \"SE6 9YU\",
          \"countryISO\": \"GB\",
          \"houseNameNumber\": \"7\",
          \"street\": \"Brick Lane\",
          \"townCity\": \"London\"
        }
      },
      \"terms\": {
        \"acceptedAt\": \"2019-08-16T18:29:02.013Z\",
        \"revision\": \"2.1\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/customers/individual"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \
  -d '{
    "roleSlug": "buyer",
    "profile": {
      "firstName": "John",
      "lastName": "Doe",
      "dob": "1986-04-29T00:00:00.000Z",
      "email": "john.doe@email.com",
      "phoneNumber": "+442078137221",
      "residentialAddress": {
        "postalCode": "SE6 9YU",
        "countryISO": "GB",
        "houseNameNumber": "7",
        "street": "Brick Lane",
        "townCity": "London"
      }
    },
    "terms": {
      "acceptedAt": "2019-08-16T18:29:02.013Z",
      "revision": "2.1"
    }
  }'

The response to a successful API call will be the full Customer object including the id, stateId, cddLevel and targetCDDLevel. Store the id in your database as you will need to pass it for any future requests associated with this Customer. The stateId will be returned as ENABLED. However, note that a Customer can only start transacting when their cddLevel equals their targetCDDLevel. If the role for your buyers has been set up to only use cards, their cdd level at creation will already be their target cdd level i.e. 0. If bank transfers are enabled, the cddLevel will initially be returned as ZERO because the due diligence checks, whilst automated, are performed asynchronously. If automated checks pass, then the cddLevel of the Customer will be updated to the targetCDDLevel shortly after creation. You can monitor asynchronous events using webhooks.

Creating accounts for buyers

Your buyers will pay on your platform via bank transfers in order to place funds in Escrow. Therefore, you will also need to create an Account for them. Buyers will be able to deposit funds into this account and pay sellers from their balance.

Generate a customer authentication token and use the Create Account API to create a new Account associating it with the user by providing the previously saved Customer id in the ownerId attribute.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c"
});

const result = await client.createAccount({
  ownerId: "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
  currencyISO: "GBP"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/accounts",
  data = json.dumps({
    "ownerId": "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
    "currencyISO": "GBP"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/accounts",
  [
    "body" => "{
      \"ownerId\": \"customer/82c6fb26-8027-454a-8451-ae37c0f9d72c\",
      \"currencyISO\": \"GBP\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/accounts"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "ownerId": "customer/82c6fb26-8027-454a-8451-ae37c0f9d72c",
    "currencyISO": "GBP"
  }'

The response to the API call will be the full Account object. You should store the account id as this will be required when you create transactions and interact with the account.

The Customers page provides further guidance on creating other types of Customers such as Incorporated Businesses and Sole Traders.

Adding bank accounts for buyers

For buyers paying in via bank transfers, you don’t need to explicitly collect the sending bank details upfront. When the buyer sends their first payment, a bank account object will automatically be created with the sender’s details and linked to the buyer as their preferred payment instrument. This bank account can later be used to process any refunds to the buyer. You can use the retrieve customer API to get the id of the preferred bank account of the buyer.

Creating Escrow payments from buyers to sellers

Having created buyers and sellers with accounts, you can now start creating escrow transactions from buyers to sellers.

The first step is to get the buyer to make a bank transfer to their account on Paybase. Call the Retrieve Reference API providing the account id of the buyer as a URI parameter.

1
2
3
4
5
6
7
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.getReference({
  toId: "account/28958679-e8a8-47a8-967c-f979ae8509a2"
});

1
2
3
4
5
6
7
8
9
10
import json
import requests

requests.get(
  "https://api-json.sandbox.paybase.io/v1/reference/account/28958679-e8a8-47a8-967c-f979ae8509a2Params: toId",
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
$client = new \GuzzleHttp\Client();
$client->request(
  "get",
  "https://api-json.sandbox.paybase.io/v1/reference/account/28958679-e8a8-47a8-967c-f979ae8509a2Params: toId",
  [
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
curl -X GET \
"https://api-json.sandbox.paybase.io/v1/reference/account/28958679-e8a8-47a8-967c-f979ae8509a2Params: toId"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \

The API call will return the bank details to which the buyer will need to make a transfer. This also includes a reference which will need to be provided on the bank transfer. You should display these bank details to the buyer instructing them to make a transfer with the reference provided. Since you provided an account id in your API request, the reference returned is a static account reference which should be used each time the buyer wants to deposit funds in their account. It is recommended that you highlight to your buyers the importance of sending the transfer with the accurate reference as otherwise the transaction will automatically be returned to the sender. Allowing users to easily copy the reference on your UI is often useful.

In Sandbox, you can simulate an incoming bank transfer from your buyers for testing purposes.

Once the funds are credited on the buyer’s account, you can create an escrow transaction between the buyer and seller. Generate a customer authentication token for the buyer and create an internal transaction from the buyer’s account to the seller’s account. You will need to provide their respective account id’s in the fromAccountId and toAccountId attributes, and set the targetStateId to HELD.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createInternalTransaction({
  fromAccountId: "account/46092ec4-83d1-4ed9-845e-a942078e62cf",
  toAccountId: "account/b199c5ac-46dd-457e-a86b-c2d9e0a39c3f",
  amount: "1000",
  targetStateId: "HELD"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/internal",
  data = json.dumps({
    "fromAccountId": "account/46092ec4-83d1-4ed9-845e-a942078e62cf",
    "toAccountId": "account/b199c5ac-46dd-457e-a86b-c2d9e0a39c3f",
    "amount": "1000",
    "targetStateId": "HELD"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/internal",
  [
    "body" => "{
      \"fromAccountId\": \"account/46092ec4-83d1-4ed9-845e-a942078e62cf\",
      \"toAccountId\": \"account/b199c5ac-46dd-457e-a86b-c2d9e0a39c3f\",
      \"amount\": \"1000\",
      \"targetStateId\": \"HELD\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/internal"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "fromAccountId": "account/46092ec4-83d1-4ed9-845e-a942078e62cf",
    "toAccountId": "account/b199c5ac-46dd-457e-a86b-c2d9e0a39c3f",
    "amount": "1000",
    "targetStateId": "HELD"
  }'

You now have a transaction going from a buyer to a seller that is currently held in escrow. At this point, these funds are not accessible by the buyer or the seller. The funds will remain in escrow until you make another API call to release this transaction based on any conditions that you choose to build on your UI (e.g. releasing a payment only when a buyer confirms an item has been received, releasing a payment 24 hours after a guest has checked into a lister’s property etc.)

When you are ready to release this payment to the seller, generate a customer authentication token for the buyer and call the transition transaction status API providing the transaction id as a URI parameter. You will transition the transaction from a state of HELD to EFFECTED which will release the original transaction and credit it to the seller’s account.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.transitionTransaction({
  id: "tx/a6af0aa7-2749-4715-b652-27c49b923421",
  toStateId: "EFFECTED"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
import requests

requests.patch(
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  data = json.dumps({
    "toStateId": "EFFECTED"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$client = new \GuzzleHttp\Client();
$client->request(
  "patch",
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  [
    "body" => "{
      \"toStateId\": \"EFFECTED\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
curl -X PATCH \
"https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer token goes here>" \
  -d '{
    "toStateId": "EFFECTED"
  }'

The above flow can also be achieved in a single step with Paybase’s Split Payments rule. The rule allows you to create a transaction before the buyer sends funds, and automatically creates an escrow transaction between the buyer and the seller as soon as the buyer’s bank transfer is received in their account. The rule can be configured on your account by Paybase, and you will be given an ‘annotation key’ for the rule (e.g. escrow).

To do this, generate a customer authentication token for the buyer and call the create inbound transaction API providing the buyer’s accountId and a targetStateId as PENDING. You will also need to provide an annotation with the amount, seller’s accountId (i.e. the final destination of the funds) and a targetStateId of HELD as in the example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createInboundTransaction({
  accountId: "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
  amount: "1000",
  method: "bankAccount",
  purpose: "PURCHASE",
  targetStateId: "PENDING",
  annotations: {
    escrow_1_accountId: "account/8810a798-3141-11ea-978f-2e728ce88125",
    escrow_1_amount: "500",
    escrow_1_targetStateId: "HELD"
  }
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  data = json.dumps({
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "annotations": {
      "escrow_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "escrow_1_amount": "500",
      "escrow_1_targetStateId": "HELD"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  [
    "body" => "{
      \"accountId\": \"account/b98774fc-3c41-43bc-bd03-13e327dcfbbb\",
      \"amount\": \"1000\",
      \"method\": \"bankAccount\",
      \"purpose\": \"PURCHASE\",
      \"targetStateId\": \"PENDING\",
      \"annotations\": {
        \"escrow_1_accountId\": \"account/8810a798-3141-11ea-978f-2e728ce88125\",
        \"escrow_1_amount\": \"500\",
        \"escrow_1_targetStateId\": \"HELD\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/inbound"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "annotations": {
      "escrow_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "escrow_1_amount": "500",
      "escrow_1_targetStateId": "HELD"
    }
  }'

Save the transaction id returned in the response.

Now, call the Retrieve Reference API providing the transaction id from above as a URI parameter.

1
2
3
4
5
6
7
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.getReference({
  toId: "tx/a6af0aa7-2749-4715-b652-27c49b923421"
});

1
2
3
4
5
6
7
8
9
10
import json
import requests

requests.get(
  "https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId",
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
$client = new \GuzzleHttp\Client();
$client->request(
  "get",
  "https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId",
  [
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
curl -X GET \
"https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \

The API call will return the bank details to which the buyer will need to make a transfer. This also includes a reference which will need to be provided on the bank transfer. You should display these bank details to the buyer instructing them to make a transfer with the reference provided. Note that this is a single use reference for this particular transaction. It is recommended that you highlight to your buyers the importance of sending the exact transaction amount with the accurate reference as otherwise the transaction will automatically be returned to the sender. Allowing users to easily copy the reference on your UI is often useful.

In Sandbox, you can simulate an incoming bank transfer from your users for testing purposes.

As soon the buyer sends a bank transfer with the exact amount and transaction reference as generated above, the original transaction state will be updated to EFFECTED and an internal escrow transaction from the buyer’s account to the seller’s account will automatically be created with a state of HELD.

As done previously, when you are ready to release this payment to the seller, generate a customer authentication token for the buyer and call the transition transaction status API providing the transaction id as a URI parameter. You will transition the transaction from a state of HELD to EFFECTED which will release the original transaction and credit it to the seller’s account.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.transitionTransaction({
  id: "tx/a6af0aa7-2749-4715-b652-27c49b923421",
  toStateId: "EFFECTED"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
import requests

requests.patch(
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  data = json.dumps({
    "toStateId": "EFFECTED"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$client = new \GuzzleHttp\Client();
$client->request(
  "patch",
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  [
    "body" => "{
      \"toStateId\": \"EFFECTED\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
curl -X PATCH \
"https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer token goes here>" \
  -d '{
    "toStateId": "EFFECTED"
  }'

Escrow payments with milestones

Milestones are useful when you want to release escrow transactions to sellers in multiple stages. E.g. in a building project, a buyer places the full amount in escrow upfront, but the seller only receives payments in stages as the project progresses and achieves pre-defined milestones.

This can be achieved with Paybase’s Split Payments rule. The rule allows you to create a transaction before the buyer sends funds, and define all the milestones. The rule can be configured on your account by Paybase, and you will be given an ‘annotation key’ for the rule (e.g. milestone).

To do this, generate a customer authentication token for the buyer and call the create inbound transaction API providing the buyer’s accountId and a targetStateId as PENDING. You will also need to provide annotations with the amount, seller’s accountId (i.e. the final destination of the funds) and a targetStateId of HELD as in the example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createInboundTransaction({
  accountId: "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
  amount: "1000",
  method: "bankAccount",
  purpose: "PURCHASE",
  targetStateId: "PENDING",
  groupId: "1001",
  annotations: {
    milestone_1_accountId: "account/8810a798-3141-11ea-978f-2e728ce88125",
    milestone_1_amount: "500",
    milestone_1_targetStateId: "HELD",
    milestone_2_accountId: "account/2dcf6444-3142-11ea-978f-3f628ce78153",
    milestone_2_amount: "300",
    milestone_2_targetStateId: "HELD",
    milestone_3_accountId: "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
    milestone_3_amount: "200",
    milestone_3_targetStateId: "HELD"
  }
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  data = json.dumps({
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "groupId": "1001",
    "annotations": {
      "milestone_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "milestone_1_amount": "500",
      "milestone_1_targetStateId": "HELD",
      "milestone_2_accountId": "account/2dcf6444-3142-11ea-978f-3f628ce78153",
      "milestone_2_amount": "300",
      "milestone_2_targetStateId": "HELD",
      "milestone_3_accountId": "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
      "milestone_3_amount": "200",
      "milestone_3_targetStateId": "HELD"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  [
    "body" => "{
      \"accountId\": \"account/b98774fc-3c41-43bc-bd03-13e327dcfbbb\",
      \"amount\": \"1000\",
      \"method\": \"bankAccount\",
      \"purpose\": \"PURCHASE\",
      \"targetStateId\": \"PENDING\",
      \"groupId\": \"1001\",
      \"annotations\": {
        \"milestone_1_accountId\": \"account/8810a798-3141-11ea-978f-2e728ce88125\",
        \"milestone_1_amount\": \"500\",
        \"milestone_1_targetStateId\": \"HELD\",
        \"milestone_2_accountId\": \"account/2dcf6444-3142-11ea-978f-3f628ce78153\",
        \"milestone_2_amount\": \"300\",
        \"milestone_2_targetStateId\": \"HELD\",
        \"milestone_3_accountId\": \"account/43b3f6a8-3142-11ea-978f-2e728ce88125\",
        \"milestone_3_amount\": \"200\",
        \"milestone_3_targetStateId\": \"HELD\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/inbound"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "groupId": "1001",
    "annotations": {
      "milestone_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "milestone_1_amount": "500",
      "milestone_1_targetStateId": "HELD",
      "milestone_2_accountId": "account/2dcf6444-3142-11ea-978f-3f628ce78153",
      "milestone_2_amount": "300",
      "milestone_2_targetStateId": "HELD",
      "milestone_3_accountId": "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
      "milestone_3_amount": "200",
      "milestone_3_targetStateId": "HELD"
    }
  }'

Save the transaction id returned in the response.

Now, call the Retrieve Reference API providing the transaction id from above as a URI parameter.

1
2
3
4
5
6
7
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.getReference({
  toId: "tx/a6af0aa7-2749-4715-b652-27c49b923421"
});

1
2
3
4
5
6
7
8
9
10
import json
import requests

requests.get(
  "https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId",
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
$client = new \GuzzleHttp\Client();
$client->request(
  "get",
  "https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId",
  [
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
curl -X GET \
"https://api-json.sandbox.paybase.io/v1/reference/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: toId"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \

The API call will return the bank details to which the buyer will need to make a transfer. This also includes a reference which will need to be provided on the bank transfer. You should display these bank details to the buyer instructing them to make a transfer with the reference provided. Note that this is a single use reference for this particular transaction. It is recommended that you highlight to your buyers the importance of sending the exact transaction amount with the accurate reference as otherwise the transaction will automatically be returned to the sender. Allowing users to easily copy the reference on your UI is often useful.

In Sandbox, you can simulate an incoming bank transfer from your users for testing purposes.

As soon the buyer sends a bank transfer with the exact amount and transaction reference as generated above the original transaction state will be updated to EFFECTED. Simultaneously, 3 separate internal escrow transactions from the buyer’s account to the seller’s account will automatically be created with a state of HELD and amounts as specified in the annotations when creating the original transaction above.

The groupId provided in the original transaction can be used to retrieve the id’s of these escrow transactions using the list transactions endpoint.

1
2
3
4
5
6
7
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const result = await client.listTransactions({
  query: "WHERE groupId = '1234' AND stateId = 'HELD'"
});

1
2
3
4
5
6
7
8
9
10
import json
import requests

requests.get(
  "https://api-json.sandbox.paybase.io/v1/txs?query=WHERE groupId = '1234' AND stateId = 'HELD'",
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<X-Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
$client = new \GuzzleHttp\Client();
$client->request(
  "get",
  "https://api-json.sandbox.paybase.io/v1/txs?query=WHERE groupId = '1234' AND stateId = 'HELD'",
  [
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<X-Token goes here>",
    ]
  ]
);

1
2
3
4
curl -X GET \
"https://api-json.sandbox.paybase.io/v1/txs?query=WHERE groupId = '1234' AND stateId = 'HELD'"  -H "Content-Type: application/json" \
  -H "X-Token: <X-Token goes here>" \

At the completion of each milestone, you can release the corresponding escrow transaction using the id of that transaction as retrieved above. Generate a customer authentication token for the buyer and call the transition transaction status API providing the transaction id of the escrow transaction as a URI parameter. You will transition the transaction from a state of HELD to EFFECTED which will release the specified escrow transaction and credit it to the seller’s account.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.transitionTransaction({
  id: "tx/a6af0aa7-2749-4715-b652-27c49b923421",
  toStateId: "EFFECTED"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
import requests

requests.patch(
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  data = json.dumps({
    "toStateId": "EFFECTED"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$client = new \GuzzleHttp\Client();
$client->request(
  "patch",
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  [
    "body" => "{
      \"toStateId\": \"EFFECTED\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
curl -X PATCH \
"https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer token goes here>" \
  -d '{
    "toStateId": "EFFECTED"
  }'

You can do the same to release the remaining escrow transactions as each milestone is completed.

Refunding escrow transactions

Completing an escrow transaction by transitioning its state from HELD to EFFECTED will move the full transaction amount to the seller’s account. However, you may sometimes wish to send only a partial amount to the seller at the end of the transaction (for e.g. a discount was agreed, the job size changed etc.).

You can do this by updating the stateId of the escrow transaction from HELD to ADJUSTED (instead of EFFECTED), and specify an adjustmentAmount. This will complete the escrow transaction by moving the original escrow amount minus the adjustmentAmount to the seller. The adjustmentAmount will be made available on the buyer’s account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.transitionTransaction({
  id: "tx/a6af0aa7-2749-4715-b652-27c49b923421",
  toStateId: "ADJUSTED",
  options: {
    adjustment: {
      adjustmentAmount: "200"
    }
  }
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json
import requests

requests.patch(
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  data = json.dumps({
    "toStateId": "ADJUSTED",
    "options": {
      "adjustment": {
        "adjustmentAmount": "200"
      }
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$client = new \GuzzleHttp\Client();
$client->request(
  "patch",
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  [
    "body" => "{
      \"toStateId\": \"ADJUSTED\",
      \"options\": {
        \"adjustment\": {
          \"adjustmentAmount\": \"200\"
        }
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
curl -X PATCH \
"https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer token goes here>" \
  -d '{
    "toStateId": "ADJUSTED",
    "options": {
      "adjustment": {
        "adjustmentAmount": "200"
      }
    }
  }'

Charging fees on escrow transactions

So far, you have been able to create escrow transactions from buyers to sellers. The full amount of the transaction was sent to sellers, however you might want to charge your platform fees on these transactions.

Paybase’s fee rules allow you to configure how you charge fees at one or multiple stages of the payment flows. For example, you want to charge your sellers a fee on each payment they receive. A fee rule can be configured on your account by Paybase, and you will be given an ‘annotation key’ for the rule (e.g. fee).

In the escrow milestones example, say you want to deduct a fee at the completion of each escrow milestone transaction instead of sending the full amount to the seller. Fees are specified at the time of setting up a transaction and therefore the structure of the API call will differ slightly.

Generate a customer authentication token for the buyer and call the create inbound transaction API providing the buyer’s accountId and a targetStateId as PENDING. This time you will provide annotations as in the example below to indicate the amounts to be sent to the seller at each milestone, and also the fee that you want to deduct.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createInboundTransaction({
  accountId: "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
  amount: "1000",
  method: "bankAccount",
  purpose: "PURCHASE",
  targetStateId: "PENDING",
  groupId: "1001",
  annotations: {
    milestone_1_accountId: "account/8810a798-3141-11ea-978f-2e728ce88125",
    milestone_1_amount: "500",
    milestone_1_fee: "10",
    milestone_1_targetStateId: "HELD",
    milestone_2_accountId: "account/2dcf6444-3142-11ea-978f-3f628ce78153",
    milestone_2_amount: "300",
    milestone_2_fee: "15",
    milestone_2_targetStateId: "HELD",
    milestone_3_accountId: "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
    milestone_3_amount: "200",
    milestone_3_fee: "20",
    milestone_3_targetStateId: "HELD"
  }
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  data = json.dumps({
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "groupId": "1001",
    "annotations": {
      "milestone_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "milestone_1_amount": "500",
      "milestone_1_fee": "10",
      "milestone_1_targetStateId": "HELD",
      "milestone_2_accountId": "account/2dcf6444-3142-11ea-978f-3f628ce78153",
      "milestone_2_amount": "300",
      "milestone_2_fee": "15",
      "milestone_2_targetStateId": "HELD",
      "milestone_3_accountId": "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
      "milestone_3_amount": "200",
      "milestone_3_fee": "20",
      "milestone_3_targetStateId": "HELD"
    }
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  [
    "body" => "{
      \"accountId\": \"account/b98774fc-3c41-43bc-bd03-13e327dcfbbb\",
      \"amount\": \"1000\",
      \"method\": \"bankAccount\",
      \"purpose\": \"PURCHASE\",
      \"targetStateId\": \"PENDING\",
      \"groupId\": \"1001\",
      \"annotations\": {
        \"milestone_1_accountId\": \"account/8810a798-3141-11ea-978f-2e728ce88125\",
        \"milestone_1_amount\": \"500\",
        \"milestone_1_fee\": \"10\",
        \"milestone_1_targetStateId\": \"HELD\",
        \"milestone_2_accountId\": \"account/2dcf6444-3142-11ea-978f-3f628ce78153\",
        \"milestone_2_amount\": \"300\",
        \"milestone_2_fee\": \"15\",
        \"milestone_2_targetStateId\": \"HELD\",
        \"milestone_3_accountId\": \"account/43b3f6a8-3142-11ea-978f-2e728ce88125\",
        \"milestone_3_amount\": \"200\",
        \"milestone_3_fee\": \"20\",
        \"milestone_3_targetStateId\": \"HELD\"
      }
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/inbound"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "1000",
    "method": "bankAccount",
    "purpose": "PURCHASE",
    "targetStateId": "PENDING",
    "groupId": "1001",
    "annotations": {
      "milestone_1_accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
      "milestone_1_amount": "500",
      "milestone_1_fee": "10",
      "milestone_1_targetStateId": "HELD",
      "milestone_2_accountId": "account/2dcf6444-3142-11ea-978f-3f628ce78153",
      "milestone_2_amount": "300",
      "milestone_2_fee": "15",
      "milestone_2_targetStateId": "HELD",
      "milestone_3_accountId": "account/43b3f6a8-3142-11ea-978f-2e728ce88125",
      "milestone_3_amount": "200",
      "milestone_3_fee": "20",
      "milestone_3_targetStateId": "HELD"
    }
  }'

Save the transaction id returned in the response.

As in the previous example, once the incoming bank transfer arrives with the exact amount and transaction reference, internal escrow transactions from the buyer’s account to the seller’s account will automatically be created with a state of HELD and amounts as specified in the annotations when creating the original transaction above.

Now, when you release an escrow transaction at the completion of each milestone by updating the state of the transaction to EFFECTED, a transfer excluding fees will be created to the seller’s account, and an adjustment on the transaction will transfer the specified fees to your own account.

Making withdrawals for sellers

With sellers having been paid in their accounts, they need to be able to withdraw these funds to their bank account.

Generate a customer authentication token for the seller and call the create outbound transaction API providing the seller’s accountId to indicate the source account and the seller’s bank account id as the paymentInstrumentId.

1
2
3
4
5
6
7
8
9
10
11
12
13
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createOutboundTransaction({
  accountId: "account/8810a798-3141-11ea-978f-2e728ce88125",
  paymentInstrumentId: "bank_account/7ef61227-0087-434f-bf8d-408c552a4efc",
  amount: "990"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/outbound",
  data = json.dumps({
    "accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
    "paymentInstrumentId": "bank_account/7ef61227-0087-434f-bf8d-408c552a4efc",
    "amount": "990"
  }),
  headers = {
    "X-Token": "<... snip ...>",
    "Content-Type": "application/json"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/outbound",
  [
    "body" => "{
      \"accountId\": \"account/8810a798-3141-11ea-978f-2e728ce88125\",
      \"paymentInstrumentId\": \"bank_account/7ef61227-0087-434f-bf8d-408c552a4efc\",
      \"amount\": \"990\"
    }",
    "headers" => [
      "X-Token" => "<... snip ...>",
      "Content-Type" => "application/json",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/outbound"  -H "X-Token: <... snip ...>" \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "account/8810a798-3141-11ea-978f-2e728ce88125",
    "paymentInstrumentId": "bank_account/7ef61227-0087-434f-bf8d-408c552a4efc",
    "amount": "990"
  }'

The API call will create an outbound payment to the seller’s bank account via Faster Payments in the UK.

Conditional Payments

The Conditional Payments workflow is an alternative way to achieve a flow that resembles escrow but is not strictly escrow from a legal standpoint.

When using Conditional Payments, you can create an escrow like flow directly from a buyer’s card instead of having to create a Paybase account for them and getting them to make a bank transfer. This also eliminates the verification requirements on buyer’s that come with creating accounts.

In the conditional payment flow, the buyer’s card will be debited but funds will not be credited immediately to the seller’s account. They will instead be HELD until you make another API call to release this transaction based on any conditions that you choose to build on your UI (e.g. releasing a payment 24 hours after a guest has checked into a lister’s property, releasing a payment only when a user confirms an item has been received etc.)

You will need to generate a customer authentication token for the buyer and call the create inbound transaction API providing the buyer’s card id and the seller’s account id. The targetStateId of the transaction will need to be set to HELD.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.createInboundTransaction({
  accountId: "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
  amount: "500",
  method: "card",
  purpose: "PURCHASE",
  targetStateId: "HELD",
  paymentInstrumentId: "card/6c1633c1-db04-4c40-b9f2-a38c65b2b7b8"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import json
import requests

requests.post(
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  data = json.dumps({
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "500",
    "method": "card",
    "purpose": "PURCHASE",
    "targetStateId": "HELD",
    "paymentInstrumentId": "card/6c1633c1-db04-4c40-b9f2-a38c65b2b7b8"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer Token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$client = new \GuzzleHttp\Client();
$client->request(
  "post",
  "https://api-json.sandbox.paybase.io/v1/txs/inbound",
  [
    "body" => "{
      \"accountId\": \"account/b98774fc-3c41-43bc-bd03-13e327dcfbbb\",
      \"amount\": \"500\",
      \"method\": \"card\",
      \"purpose\": \"PURCHASE\",
      \"targetStateId\": \"HELD\",
      \"paymentInstrumentId\": \"card/6c1633c1-db04-4c40-b9f2-a38c65b2b7b8\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer Token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
8
9
10
11
12
curl -X POST \
"https://api-json.sandbox.paybase.io/v1/txs/inbound"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer Token goes here>" \
  -d '{
    "accountId": "account/b98774fc-3c41-43bc-bd03-13e327dcfbbb",
    "amount": "500",
    "method": "card",
    "purpose": "PURCHASE",
    "targetStateId": "HELD",
    "paymentInstrumentId": "card/6c1633c1-db04-4c40-b9f2-a38c65b2b7b8"
  }'

The response to a successful API request will be the full transaction object. The transaction state will be HELD indicating that the transaction was successfully debited from the user’s card.

If 3D Secure authentication is activated for transactions on your account, the transaction will be created in a PROCESSING state and the object will return the 3D Secure authentication URL in the requirements attribute. In this case, you will need to handle the 3DS transaction flow. When the 3DS flow is completed successfully, the state will be updated to HELD.

When you are ready to release this payment to the seller, generate a customer authentication token for the buyer and call the transition transaction status API providing the transaction id as a URI parameter. You will transition the transaction from a state of HELD to EFFECTED which will release the original transaction and credit it to the seller’s account.

1
2
3
4
5
6
7
8
9
10
11
12
import { v1 } from '@paybase/client';

const client = v1('<- API Key ->', { sandbox: true });

const { accessToken } = await client.createCustomerAuthenticationToken({
  id: "<- Sending Customer ID ->"
});

const result = await client.transitionTransaction({
  id: "tx/a6af0aa7-2749-4715-b652-27c49b923421",
  toStateId: "EFFECTED"
}, { apiKey: accessToken });

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
import requests

requests.patch(
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  data = json.dumps({
    "toStateId": "EFFECTED"
  }),
  headers = {
    "Content-Type": "application/json",
    "X-Token": "<Customer token goes here>"
  }
).json()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$client = new \GuzzleHttp\Client();
$client->request(
  "patch",
  "https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state",
  [
    "body" => "{
      \"toStateId\": \"EFFECTED\"
    }",
    "headers" => [
      "Content-Type" => "application/json",
      "X-Token" => "<Customer token goes here>",
    ]
  ]
);

1
2
3
4
5
6
7
curl -X PATCH \
"https://api-json.sandbox.paybase.io/v1/tx/tx/a6af0aa7-2749-4715-b652-27c49b923421Params: id/state"  -H "Content-Type: application/json" \
  -H "X-Token: <Customer token goes here>" \
  -d '{
    "toStateId": "EFFECTED"
  }'

Webhooks

You should also ensure that you have a webhook set up to listen to some events that will allow you to provide a better experience to your users. Paybase will whitelist the URLs to which you want events to be posted.

In particular, you should consider listening to the following webhooks:

  • customer_cdd_level_changed to keep track of the cdd level of customers and know when they can start transacting
  • check_failed to be notified if any of the automated due diligence checks failed and therefore require additional information
  • alert_action_required if an alert has been raised that requires actioning on the Console before the associated event can be completed (e.g. transaction paused, customer verification failed etc.)
  • transaction_failed to monitor any transactions that failed, particular those that were refunded to sender due to an incorrect reference
  • account_balance_updated to know when a buyer’s bank transfer has been credited to their Paybase account

Other considerations

  • Strong Customer Authentication - You should ensure that your platform complies with SCA requirements as detailed in the SCA guide.
  • Front End Checklist - Please refer to general guidance on front end requirements that you need to comply with when building your platform.
  • Handle Refunds to make sure you can provide refunds to your buyers if required.
  • Uploading Documents - useful for uploading additional verification documents required when automated checks fail. This can also be done manually on the Console.