Building a Web3 Peer to Peer Payments App

25002500

Conceptual Overview

By combining the power of many of Wyre's APIs, our partners can build apps that allow end users to send and receive funds similar to many of the popular peer-to-peer payments apps that have become household names.

With Wyre's Users API, we allow partners and developers to safely and compliantly KYC and onboard their users as well as store funds for users that fulfill KYC requirements.

Wyre's Payment Method API allows bank accounts to be attached to every user that passes KYC.

Wyre's Transfers API enables fiat funds onboarding via various banking methods as well as sending payments to other users or exchanging currencies between users or within the same user without a blockchain network fee.

Step 1: Create a Wyre Account and Generate Keys

Go to Set Up Your Account to learn how to generate keys and start building on TestWyre!

Step 2: Create a User

Create a User, first and last name recommended as default.

{
    "fields": {
        "firstName": "Alberta",
        "lastName": "Charleson"
    },
    "blockchains": [
        "ALL"
    ]
}
{
    "id": "US_39CUCTGTVBB",
    "status": "APPROVED",
    "partnerId": "PT_UUEJLH74VEN",
    "type": "INDIVIDUAL",
    "createdAt": 1648361213033,
    "depositAddresses": {
        "BTC": "miFkpeaFfrLG9A7owsHWQnj8iXitsdB4Gk",
        "MATIC": "0xf40501f440eb0c596ee053f88bd43ba9ae136b56",
        "AVAX": "0xe494a6d8d10f23630ba9f0b99bf455e9d4951abc",
        "ETH": "0x6c70a5acb186d4c447719d24e31a2a80f1539dc1",
        "XLM": "GD7WXI7AOAK2CIPZVBEFYLS2NQZI2J4WN4HFYQQ4A2OMFVWGWAL3IW7K:V9PMQ8EYEWC",
        "AVAXC": "0xf01724bb979df8ff888805809a014d93087f8d24"
    },
    "totalBalances": {},
    "availableBalances": {},
    "fields": {
        "firstName": {
            "value": "Alberta",
            "error": null,
            "status": "SUBMITTED"
        },
        "lastName": {
            "value": "Charleson",
            "error": null,
            "status": "SUBMITTED"
        },
        "dateOfBirth": {
            "value": "",
            "error": null,
            "status": "OPEN"
        },
        "residenceAddress": {
            "value": {""
            },
            "error": null,
            "status": "OPEN"
        }
    }
}

Step 3: Generate Onboarding Url

Generate an Get KYC Onboarding URL to fully KYC the User.

curl --request GET \
     --url https://api.testwyre.com/v3/users/US_X3RCVXREFCA/onboarding \
     --header 'Accept: application/json' \
     --header 'Authorization: Bearer null'
{
  "onboardingUrl": "https://pay.testwyre.com/users/US_X3RCVXREFCA/onboarding/TYAQL6BPRNQCJVQWZNU2CQUD6JTT4ATF49WZJXCHYB6XNR2LU9"
}

Step: 4 Verify User Status

Set up User webhooks to receive automated updates when a User status changes.

{
  "userId":"US_9UG6JF2Y26T",
  "type": "USER_STATUS_UPDATED"
}

Use the webhook to trigger a query of the User status with Get User. Make sure to use the 'scopes' Url parameter to check the status of the particular functionality your User is attempting to get approved for.

curl --location --request GET 'https://api.testwyre.com/v3/users/US_9UG6JF2Y26T?scopes=ACH' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data-raw ''

Validate that the status is 'Approved'

{
    "id": "US_NDTJFWMRH3G",
    "status": "APPROVED",
    "partnerId": "PT_6NU9NYY3XRU",
    "type": "INDIVIDUAL",
    "createdAt": 1653678433000,
    "depositAddresses": {
        "BTC": "mzq6mcgyNvREve5ofEp1tmtBTD8h9Rzxm5",
        "MATIC": "0x0a8040e58b947d30a3f09fd4cb17c94a969054ec",
        "AVAX": "X-fuji1afarwjk5srh6ckhyy03ptyxrdkj602dhglznlx",
        "ETH": "0x7107cee70690c5c9b18f58500e1f27e3c7640640",
        "XLM": "GD7WXI7AOAK2CIPZVBEFYLS2NQZI2J4WN4HFYQQ4A2OMFVWGWAL3IW7K:R6TCNAJ8GHE",
        "AVAXC": "0xb5599a7ce6b82858c6edc25725d7fe49262c5811"
    },
    "totalBalances": {},
    "availableBalances": {},
    "fields": {
        "legalName": {
            "value": "Tim Cooke",
            "error": null,
            "status": "SUBMITTED"
        },
        "cellphoneNumber": {
            "value": "+14154567895",
            "error": null,
            "status": "SUBMITTED"
        },
        "email": {
            "value": "[email protected]",
            "error": null,
            "status": "SUBMITTED"
        },
        "residenceAddress": {
            "value": {
                "street1": "342 California St",
                "city": "san francisco",
                "state": "CA",
                "postalCode": "95115",
                "country": "US"
            },
            "error": null,
            "status": "SUBMITTED"
        },
        "ssn": {
            "value": "REDACTED",
            "error": null,
            "status": "SUBMITTED"
        }
    }
}

Step 5: Connect Bank Account

By masquerading as the user, we can attach a payment method using our Create Payment Method API via two methods:

The first method is creating a Plaid processor token and then passing it to Wyre via the API.

πŸ“˜

Plaid Required for ACH Onramp

For chargeable ACH payment methods it is required to link the bank account using Plaid.

#first create processor token via plaid

curl --location --request POST 'https://sandbox.plaid.com/processor/token/create' \
--header 'Content-Type: application/json' \
--data-raw '{
    "client_id": "60f5d817da3e5300110xxxx",
    "secret": "a0adfa408349072862bbb7xxxxxx",
    "access_token": "access-sandbox-bd8a8917-84bd-4d1a-b231-671ecf3fb850",
    "account_id": "DmzZEm5kA7Hl3yepvXN4hQ4lj6yrGXhXVPNKk",
    "processor": "wyre"
  }'
  
  #response:
  {
    "processor_token": "processor-sandbox-af653618-f9e4-41ad-a567-03109952b2b3",
    "request_id": "GssgMK1wnpPBeiK"
}

#Take the processor token from the response and send it to create payment method API
#masquerading as the User

curl --location --request POST 'https://api.testwyre.com/v2/paymentMethods?masqueradeAs=user:US_39CUCTGTVBB' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-XXX' \
--header 'Content-Type: application/json' \
--data-raw '{
     "plaidProcessorToken": "processor-sandbox-af653618-f9e4-41ad-a567-03109952b2b3",
     "paymentMethodType": "LOCAL_TRANSFER",
     "country": "US"
}'

#response:
{
    "id": "PA_DZ9WJXQEPXB",
    "owner": "user:US_39CUCTGTVBB",
    "createdAt": 1648364651822,
    "name": "Plaid Checking 0000",
    "defaultCurrency": "USD",
    "fingerprint": "EH6HMC/UfK+45MdA9s+RlVE3mzbROARQiT5KGh26c94=",
    "status": "PENDING",
    "statusMessage": null,
    "waitingPrompts": [],
    "linkType": "LOCAL_TRANSFER",
    "beneficiaryType": "UNKNOWN",
    "supportsDeposit": true,
    "nameOnMethod": null,
    "last4Digits": "0000",
    "brand": null,
    "expirationDisplay": null,
    "countryCode": "US",
    "nickname": null,
    "rejectionMessage": null,
    "disabled": false,
    "supportsPayment": true,
    "chargeableCurrencies": [
        "USD"
    ],
    "depositableCurrencies": [
        "USD"
    ],
    "chargeFeeSchedule": null,
    "depositFeeSchedule": null,
    "minCharge": null,
    "maxCharge": null,
    "minDeposit": null,
    "maxDeposit": null,
    "documents": [],
    "blockchains": {},
    "liquidationBalances": {},
    "srn": "paymentmethod:PA_DZ9WJXQEPXB"
}

🚧

Offramp Only ACH

For non-chargebable ACH payment methods (chargeablePM = false), as an alternative to Plaid you can connect a bank account by passing in bank and beneficiary information.

## leave paymentMethodType as INTERNATIONAL_TRANSFER regardless of users location
curl --location --request POST 'https://api.testwyre.com/v2/paymentMethods?masqueradeAs=user:US_39CUCTGTVBB' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-xxx' \
--header 'Content-Type: application/json' \
--data-raw '{
     "paymentMethodType": "INTERNATIONAL_TRANSFER", 
     "paymentType": "LOCAL_BANK_WIRE",
     "currency": "USD",
     "country": "US",
     "beneficiaryType": "INDIVIDUAL",
     "firstNameOnAccount": "Alberta",
     "lastNameOnAccount": "Charleson",
     "beneficiaryAddress": "2992 Cameron Road",
     "beneficiaryAddress2": "",
     "beneficiaryCity": "Malakoff",
     "beneficiaryPostal": "14236",
     "beneficiaryPhoneNumber": "+15555555555",
     "beneficaryState": "NY",
     "beneficiaryDobDay": 2,
     "beneficiaryDobMonth": 12,
     "beneficiaryDobYear": 1990,
     "accountNumber": "1234567890123",
     "routingNumber": "123412312",
     "accountType": "CHECKING",
     "chargeablePM": false
}'

#response:
{
    "id": "PA_PTXN82CCWRH",
    "owner": "user:US_39CUCTGTVBB",
    "createdAt": 1648783498152,
    "name": "USD Bank account ending in 0123",
    "defaultCurrency": "USD",
    "fingerprint": "BANK-bZwB7bB30jWoskzil4q13Vd9oxqn4g3mLhdUGd+dDIA=",
    "status": "AWAITING_FOLLOWUP",
    "statusMessage": null,
    "waitingPrompts": [
        {
            "id": "XGBPVZFYHYY",
            "prompt": "Upload a bank statement",
            "responses": [],
            "type": "DOCUMENT"
        }
    ],
    "linkType": "INTERNATIONAL_TRANSFER",
    "beneficiaryType": "INDIVIDUAL",
    "supportsDeposit": true,
    "nameOnMethod": "Alberta Charleson",
    "last4Digits": "0123",
    "brand": null,
    "expirationDisplay": null,
    "countryCode": "US",
    "nickname": null,
    "rejectionMessage": null,
    "disabled": false,
    "supportsPayment": false,
    "chargeableCurrencies": [],
    "depositableCurrencies": [
        "USD"
    ],
    "chargeFeeSchedule": null,
    "depositFeeSchedule": null,
    "minCharge": null,
    "maxCharge": null,
    "minDeposit": null,
    "maxDeposit": null,
    "documents": [],
    "blockchains": {},
    "liquidationBalances": {},
    "srn": "paymentmethod:PA_PTXN82CCWRH"
}

Step 6: Create a User Wallet

Use Wallets with masquerading to assign the wallet to the user.

curl --location --request POST 'https://api.testwyre.com/v2/wallets?masqueradeAs=user:US_CELH74LZG4A' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data-raw '{
     "name": "wallet 1 US_CELH74LZG4A",
     "callbackUrl": "https://requestinspector.com/inspect",
     "notes": "Notes about the wallet"
}'
{
    "pusherChannel": "2c6ed35b5cbb4e7c7478b0238a7d26a1",
    "balances": {},
    "srn": "wallet:WA_N2B76P6BZ8U",
    "callbackUrl": "https://requestinspector.com/inspect",
    "notes": "Notes about the sub account",
    "depositAddresses": {
        "BTC": "mtm3sMtYU499d6yURr86g48CBtyDkTh7ho",
        "MATIC": "0x5e364e231a79ff0da9d3150239afb41d4c6295c7",
        "AVAX": "X-fuji1lh4qjuc9a206ju5neezuw8hqfvmwq2lls90h0p",
        "ETH": "0x275b668bdbba8d1285feb7c3861f3bea22b31e9f",
        "XLM": "GD7WXI7AOAK2CIPZVBEFYLS2NQZI2J4WN4HFYQQ4A2OMFVWGWAL3IW7K:WTE3NZUYW8P",
        "AVAXC": "0x5a66421bd1856b375c823cd299b36f9425f700f5"
    },
    "totalBalances": {},
    "availableBalances": {},
    "savingsReferralSRN": null,
    "name": "wallet 1 US_LWYHFQTFTZJ",
    "id": "WA_N2B76P6BZ8U",
    "type": "DEFAULT",
    "status": null
}

Step 7: Onramp User Funds to Wallet

You can pull funds into Wyre using a payment method as the source.

curl --location --request POST 'https://api.testwyre.com/v3/transfers' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-xxx' \
--header 'Content-Type: application/json' \
--data-raw '{
     "source": "paymentmethod:PA_DZ9WJXQEPXB",
     "sourceCurrency": "USD",
     "sourceAmount": "200",
     "dest": "wallet:WA_N2B76P6BZ8U",
     "destCurrency": "BTC",
     "message": "buying btc",
     "autoConfirm": true
}'

Step 8: Move Funds around Wyre

Through Wyre's Transfers API you can move and exchange funds in and out of the platform. Common transfer types include user to user transfers, crypto exchange and offramp bank to the user's payment method.

# a simple fiat to fiat transfer similar to a cash app/venmo payment

curl --location --request POST 'https://api.testwyre.com/v3/transfers' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-xxx' \
--header 'Content-Type: application/json' \
--data-raw '{
     "source": "wallet:US_39CUCTGTVBB",
     "sourceCurrency": "USD",
     "sourceAmount": "20",
     "dest": "wallet:WA_648UELU24CQ",
     "destCurrency": "USD",
     "message": "Sending USD to WA_648UELU24CQ",
     "autoConfirm": true
}'
# doing a currency exchange for a user

curl --location --request POST 'https://api.testwyre.com/v3/transfers' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-xxx' \
--header 'Content-Type: application/json' \
--data-raw '{
     "source": "wallet:WA_N2B76P6BZ8U",
     "sourceCurrency": "ETH",
     "sourceAmount": "0.01",
     "dest": "wallet:WA_N2B76P6BZ8U",
     "destCurrency": "BTC",
     "message": "converting eth to BTC for user:US_39CUCTGTVBB",
     "autoConfirm": true
}'
# paying out to bank account

curl --location --request POST 'https://api.testwyre.com/v3/transfers' \
--header 'Authorization: Bearer SK-86Z8FYFB-UTA2RDCN-HCZBFT2V-xxx' \
--header 'Content-Type: application/json' \
--data-raw '{
     "source": "wallet:WA_N2B76P6BZ8U",
     "sourceCurrency": "USD",
     "sourceAmount": "20",
     "dest": "paymentmethod:PA_DZ9WJXQEPXB",
     "destCurrency": "USD",
     "message": "sending USD to bank account",
     "autoConfirm": true
}'

Step 9: Add Blockchains (Optional)

For Algorand and Flow addresses you must opt in to generate a wallet address, use the Wallets attach endpoint for this.

curl --location --request POST 'https://api.testwyre.com/v3/blockchain/attach?masqueradeAs=user:US_LWYHFQTFTZJ' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer ' \
--data-raw '{
    "targetSrn": "wallet:WA_N2B76P6BZ8U",
    "blockchain": "ALGO"
}'
{
    "ALGO": "OW5S25V7DEK7EXMZU2E6F26SYMM32JGXV2A23MGIH3E3IO2ACJLH4TS53Q"
}