SWIFT Payments on Behalf of Customers

This guide outlines how to make SWIFT payments on behalf of your business customers using the Keyrails API. These are POBO (Pay On Behalf Of) payments, where your customer's name appears as the sender.

Overview

To initiate a POBO SWIFT payment for a customer, follow these steps:

  1. Create a Customer
  2. Create a Wallet
  3. Check wallet balance
  4. Create a SWIFT Transfer
  5. Monitor Transfer Status

Step 1: Create a Customer

πŸ“˜

Onboard using KYB link

Instead of integrating the Customer API directly to onboard and verify (KYC/KYB) customer, you can generate onboarding Links for a faster setup. See Guide here

Note: customer onboarding functionality is currently unavailable for U.S. residents.

If withLivenessCheck=FALSE, then:

  • On step 1b : identityDocumentFront must be provided
  • On step 1d : directorsLivenessCheck and shareHoldersLivenessCheck must be provided.

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/customers \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --data '
{
  "businessLegalName": "string",
  "businessTradeName": "string",
  "taxIdentificationNumber": "string",
  "website": "string",
  "incorporationDate": "1999-03-21",
  "companyRegistrationNumber": "string",
  "businessIndustries": [
    "AccountFunding"
  ],
  "businessModel": "string",
  "customerJurisdictions": [
    "NG", "GB"
  ],
  "regulatedEntity": "string",
  "notRegulatedReason": "string",
  "sourceOfFunds": "string",
  "accountPurpose": "string",
  "email": "string",
  "transactionOriginCountries": [
    "GB"
  ],
  "transactionDestinationCountries": [
    "GB", "NG"
  ],
  "companySourceOfWealthExplanation": "string",
  "companySourceOfWealthEvidence": "Base64String",
  "shareholdersSourceOfWealthExplanation": "string",
  "shareholdersSourceOfWealthEvidence": "Base64String",
  "fundingSourceEvidence": "Base64String",
  "fundingSourceExplanation": "string",
	"countryOfIncorporation": "GA",
  "registeredAddress": {
    "street1": "string",
    "city": "string",
    "country": "CA"
  },
  "operationalAddress": {
    "street1": "string",
    "city": "string",
    "country": "CA"
  },
  "contactPerson": {
    "firstName": "string",
    "lastName": "string",
    "phoneNumber": "+44791234560",
    "email": "string"
  },
  "businessIndustryType": "CorporateOrMerchant",
  "withLivenessCheck": true,
  "termsOfServiceAcceptance": {
    "date": "2025-07-28T20:00:44.191Z",
    "ipAddress": "201.221.240.218"
  },
  "customerBaseBreakdown": "Retail",
  "isBusinessRegulated": true,
  "compliance": {
    "hasFinancialCrimeHistoryLast5Years": false,
    "isNegativeNewsAndSanctionsScreeningPerformed": false,
    "isTransactionMonitoringOrBlockchainAnalyticsPerformed": true,
    "isKYCPerformed": true,
    "financialCrimeProceedingsDescription": "string",
    "negativeNewsAndSanctionsVendor": "string",
    "transactionMonitoringOrBlockchainAnalyticsVendor": "string",
    "kycVendor": "string"
  },
  "transactionBreakdown": {
    "stablecoinTxCountMonthly": "Range1To10",
    "incomingStablecoinAvgUsdValue": "Usd15kTo50k",
    "outgoingStablecoinTxCountMonthly": "Range1To10",
    "outgoingStablecoinAvgUsdValue": "Usd15kTo50k",
    "incomingAchTxCountMonthly": "Range1To10",
    "incomingAchAvgUsdValue": "Usd15kTo50k",
    "outgoingAchTxCountMonthly": "Range1To10",
    "outgoingAchAvgUsdValue": "Usd15kTo50k",
    "incomingDomesticWireTxCountMonthly": "Range1To10",
    "incomingDomesticWireAvgUsdValue": "Usd15kTo50k",
    "outgoingDomesticWireTxCountMonthly": "Range1To10",
    "outgoingDomesticWireAvgUsdValue": "Usd15kTo50k",
    "incomingInternationalWireTxCountMonthly": "Range1To10",
    "incomingInternationalWireAvgUsdValue": "Usd15kTo50k",
    "outgoingInternationalWireTxCountMonthly": "Range1To10",
    "outgoingInternationalWireAvgUsdValue": "Usd15kTo50k",
    "estimatedMonthlyVolumeUsd": "Usd500kTo1m",
    "transactionTypes": ["StablecoinTransactions"],
    "preferredSettlementCurrencies": ["USD"]
  }
}
'

Response

{
  "customerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Step 1b: Add Associated Persons

If withLivenessCheck=FALSE when creating a customer in step 1, then identityDocumentFront must be provided.

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/customers/{customerId}/associated-persons \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --data '
{
  "firstName": "string",
  "lastName": "string",
  "email": "string",
  "dateOfBirth": "1988-10-28",
  "shareHolderPercentage": 25,
  "address": {
    "street1": "string",
    "city": "string",
    "state": "string",
    "country": "string"
  },
  "identity": {
    "type": "Passport",
    "countryCode": "JP",
    "identityNumber": "string",
    "identityDocumentFront": "data:image/pdf;base64,abdcxxxx",
    "identityDocumentBack": "data:image/pdf;base64,abdcxxxx"
  }
}
'

Response

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "verificationUrl": "string" // when withVerification=TRUE
}

Step 1c: Add Associated Entities

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/customers/{customerId}/associated-entities \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --data '
{
  "entityLegalName": "string",
  "shareHolderPercentage": 25,
  "businessTradeName": "string",
  "incorporationNumber": "string",
  "relationshipEstablishedAt": "1988-10-28",
  "countryOfRegistration": "AL",
  "registeredAddress": {
    "street1": "string",
    "city": "string",
    "state": "string",
    "country": "CA"
  }
}
'

Response

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}

Step 1d: Upload documents

  • Required file types: organizationChart, shareholdersProofOfAddress, directorsProofOfAddress, ownershipChart, shareholdersRegistry,directorsRegistry, companyProofOfAddress, businessRegistrationDocument, articlesOfAssociationOrMemorandum,
  • If withLivenessCheck=FALSE when creating a customer in step 1, then both directorsLivenessCheck and shareHoldersLivenessCheck must be provided.

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/customers/{customerId}/documents \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --data '
{
  "files": [
     // ---------- REQUIRED FILES ----------
        {
            "fileType": "organizationChart",
            "fileName": "OrganizationChart.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "shareholdersProofOfAddress",
            "fileName": "ShareholdersProofOfAddress.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "directorsProofOfAddress",
            "fileName": "DirectorsProofOfAddress.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "ownershipChart",
            "fileName": "OwnershipChart.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "shareholdersRegistry",
            "fileName": "ShareholdersRegistry.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "directorsRegistry",
            "fileName": "DirectorsRegistry.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "companyProofOfAddress",
            "fileName": "CompanyProofOfAddress.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "businessRegistrationDocument",
            "fileName": "BusinessRegistrationDocument.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "articlesOfAssociationOrMemorandum",
            "fileName": "ArticlesOfAssociationOrMemorandum.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        // ---------- OPTIONAL FILES ----------
        {
            "fileType": "other",
            "fileName": "Other.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyBusinessRegistrationDocument",
            "fileName": "ParentCompanyBusinessRegistrationDocument.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyShareholdersRegistry",
            "fileName": "ParentCompanyShareholdersRegistry.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyDirectorsRegistry",
            "fileName": "ParentCompanyDirectorsRegistry.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyShareholdersProofOfAddress",
            "fileName": "ParentCompanyShareholdersProofOfAddress.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyDirectorsProofOfAddress",
            "fileName": "ParentCompanyDirectorsProofOfAddress.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyShareholdersId",
            "fileName": "ParentCompanyShareholdersId.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "parentCompanyDirectorId",
            "fileName": "ParentCompanyDirectorId.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "complianceOfficerCV",
            "fileName": "ComplianceOfficerCV.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "independentAmlAudit",
            "fileName": "IndependentAmlAudit.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "amlPolicy",
            "fileName": "AmlPolicy.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        },
        {
            "fileType": "regulatoryLicenses",
            "fileName": "RegulatoryLicenses.pdf",
            "fileBlob": "data:image/pdf;base64,abdcxxxx"
        }
  ]
}
'

Step 1e: Complete and submit onboarding

Finalise and submit the full customer onboarding package for review.

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/customers/{customerId}/submit \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json'

πŸ“˜

Simulation - update customer status

Note you can update the customer status using the status simulation endpoint. See API Reference

Monitor Customer onboarding Status

See API Reference

curl --request GET \
     --url https://api.sandbox.keyrails.com/api/v1/customers/{customerId} \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json'
Customer Webhooks Examples

Before proceeding, ensure that your webhook configuration is set up. Refer to the setup guide for detailed instructions.

  • Webhook types: Identity

  • resourceId: References the customer ID

    Approved:

    {
      "tenantId": "36a6deef-8d5a-4560-b17d-e73f1dd3cd88",
      "action": "Update",
      "id": "05dfa0a6-0e9a-4891-9b20-42b5b2c39dbd",
      "resourceId": "d0ffaf3b-a73b-461b-a82b-c2cff911cb13",
      "resourceType": "Identity",
      "createdAtUtc": "2025-07-29T13:58:56.8454302Z",
      "changes": {
        "complianceStatus": "Approved"
      }
    }

    Rejected:

    {
      "tenantId": "36a6deef-8d5a-4560-b17d-e73f1dd3cd88",
      "action": "Update",
      "id": "c7f89968-d91f-4b2f-80c6-9ea2e1d6ad8a",
      "resourceId": "d0ffaf3b-a73b-461b-a82b-c2cff911cb13",
      "resourceType": "Identity",
      "createdAtUtc": "2025-07-29T14:00:26.1457208Z",
      "changes": {
        "complianceStatus": "Rejected",
        "rejectionReasons": []
      }
    }

Step 2: Create a wallet

Create a wallet to hold funds for the customer.

See API Reference

curl --request POST \
  --url https://api.sandbox.keyrails.com/api/v1/wallets \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer {{AccessToken}}'  \
  --data '{
   "customerId": "f3675f2e-da61-42e9-a9db-eef8bd4fb4e2",
   "network": "Sepolia" // Ethereum for production
}'

Response:

{
    "id": "d3a685f1-5803-4dc4-b63f-d9281879a686",
    "createdAtUtc": "2025-08-21T09:03:35.356903+00:00",
    "status": "Active",
    "customerId": "6da60529-9ba5-4370-88f7-a23e96041707",
    "cryptoDepositInstructions": [
        {
            "network": "Sepolia",
            "address": "0xafabec37213042e92af9d88e81dd00ea6120ab7d"
        }
    ]
}

Add some test funds - Top up wallet balance

See API Reference

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/wallets/simulate-balance \
     --header 'Authorization: Authorization: Bearer {{AccessToken}}' \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --data '
{
  "walletAddress": "0x4d0280da2f2fDA5103914bCc5aad114743152A9c",
  "amount": "50000000",
  "tokenType": "USDC" // or USDT
}
'

Step 3: Check wallet balance

See API Reference

curl --request GET \
  --url https://api.sandbox.keyrails.com/api/v1/wallets/{walletId}/balance \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer {{AccessToken}}'

Response:

[
  {
      "currency": "USDT",
      "network": "Sepolia",
      "total": 0
  },
  {
      "currency": "USDC",
      "network": "Sepolia",
      "total": 0
  }
]

πŸ“˜

Internal Wallet Transfers

You can transfer funds between a customer’s wallet and a tenant wallet.

See Internal transfer API


Wallet Deposit Webhooks Example

Before proceeding, ensure that your webhook configuration is set up. Refer to the setup guide for detailed instructions.

  • Webhook types: Transaction

  • resourceId: References the transaction Id

    Crypto deposited:

    {
      "tenantId": "2711ad4d-2c6b-4238-9f9d-2381d7a6d214",
      "action": "Update",
      "id": "d58d77b2-396e-44b1-a918-90ca655d498b",
      "resourceId": "58a8bedd-7bda-4471-936d-f9d32191524f",
      "resourceType": "Transaction",
      "createdAtUtc": "2025-07-29T14:15:25.7333739Z",
      "changes": {
        "transactionStatus": "Completed",
        "transactionType": "DepositCrypto"
      }
    }	

Step 4: Initiate SWIFT Transfer

Send funds from the wallet to an external bank account via SWIFT.

See API reference.

curl --request POST \
     --url https://api.sandbox.keyrails.com/api/v1/transfers \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --header 'Authorization: Bearer {{AccessToken}}' \
     --data '{
    "customerId": "string",
    "source": {
      "currency": "USDC",
      "network": "Sepolia" // Ethereum for production
      "walletId": "string"
    },
    "destination": {
      "currency": "GBP",
      "receiver": {
        "name": "string",
        "phone": "+447912345678",
        "address": {
          "street1": "string",
          "street2": "string", // optional
          "postalCode": "string", // optional
          "city": "string",
          "state": "string", // optional
          "country": "string"
        }
      },
      "bank": {
        "name": "string",
        "address": {
          "street1": "string",
          "street2": "string", // optional
          "postalCode": "string", // optional
          "city": "string",
          "state": "string", // optional
          "country": "string"
        },
        "accountNumber": "string", // optional - when iban is provided
        "iban": "string", // optional - when accountNumber is provided
        "bic": "string"
      }
    },
    "purposeOfPayment": "PaymentForGoods",
    "receiverAmount": 5000.00, // Or receiverAmount in destination currency eg. GBP
    "memo": "INV-001",
    "documents": ["data:image/[type];base64,abdcxxxx", "data:image/[type];base64,abdcxxxx"]
}
'

Response:

The API responds with a transactionId, which can be used to track the transfer.

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "New",
   ...
}

πŸ“˜

Transfer Quote

Use the transfer quote endpoint to see the transfer quote before creating a transfer. See Transfer Quote API


Monitor Transfer Status

See API reference.

curl --request GET \
     --url https://api.sandbox.keyrails.com/api/v1/transactions/{transactionId} \
     --header 'accept: application/json' \
     --header 'content-type: application/*+json' \
     --header 'Authorization: Bearer {{AccessToken}}' \

Response:

The API responds with a transactionId, which can be used to track the transfer.

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "transactionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "status": "New",
  "trackingStatus": "string",
  "trackingReference": "string",
  "currency": "USD",
  "sourceCurrency": "USD",
  "destinationCurrency": "USD",
  "network": "string",
  "amount": 0,
  "cryptoAmount": 0,
  "transactionFee": 0,
  "appliedFee": 0,
  "totalAmount": 0,
  "comment": "string",
  "memo": "string",
  "purposeOfPayment": "string",
  "country": "string",
   ...
}

Transfer webhooks example

Before proceeding, ensure that your webhook configuration is set up. Refer to the setup guide for detailed instructions.

  • Webhook types: Transaction
  • resourceId: References the transaction ID

Processing:

{
  "tenantId": "2711ad4d-2c6b-4238-9f9d-2381d7a6d214",
  "action": "Create",
  "id": "e95503c4-f8bb-4f6b-89d3-42f10307a6bc",
  "resourceId": "b5787a92-82ba-4f09-bacc-02f57ea601ed",
  "resourceType": "Transaction",
  "createdAtUtc": "2025-07-29T13:47:04.7710866Z",
  "changes": {
    "transactionStatus": "Processing",
    "transactionType": "SettlementGlobalConnectUsdc",
  }
}

Completed:

{
  "tenantId": "36a6deef-8d5a-4560-b17d-e73f1dd3cd88",
  "action": "Update",
  "id": "4c01fdd9-923d-4ad9-9508-48a39cc85a3b",
  "resourceId": "6470d2bd-89e3-4e10-a3ff-605d820f8da3",
  "resourceType": "Transaction",
  "createdAtUtc": "2025-07-29T14:06:04.1804263Z",
  "changes": {
    "transactionStatus": "Completed",
    "transactionType": "SettlementGlobalConnectUsdc",
  }
}

Failed:

{
  "tenantId": "36a6deef-8d5a-4560-b17d-e73f1dd3cd88",
  "action": "Update",
  "id": "44110cf3-05c4-4652-b718-fc0d6475453f",
  "resourceId": "6470d2bd-89e3-4e10-a3ff-605d820f8da3",
  "resourceType": "Transaction",
  "createdAtUtc": "2025-07-29T14:07:01.6713778Z",
  "changes": {
    "transactionStatus": "Failed",
    "transactionType": "SettlementGlobalConnectUsdc",
  }
}