Skip to content
Select theme

Quickstart: your first sandbox payment

This quickstart takes you from an API key to a live transaction in the GrailPay sandbox. You authenticate, onboard the two sides of a payment, register a webhook, create a transaction, and follow it as it moves. Nothing you send here reaches a real financial institution, so you can run the full flow safely.

By the end you will have:

  • Confirmed your API key with an authenticated call.
  • Onboarded a merchant and a person, and used them as the two sides of a payment. Here the person pays the merchant; the same two records work the other way around.
  • Registered a webhook endpoint to receive event updates.
  • Created a transaction and watched it move through its first states.

This walkthrough uses one common shape, a person paying a merchant, but the rule behind it is broader: every payment except a person-to-person payment needs a KYB-verified merchant on at least one side. The merchant can be the payor or the payee, and the other side can be a person, a business, or another merchant. The two UUIDs you send in Step 5 are all that decide who pays whom. For the full model behind each step, see the Payments Platform Overview.

Before you start, you need:

  • A sandbox API key. If you do not have one, contact support@grailpay.com.
  • A tool for sending HTTPS requests. This guide uses cURL; any HTTP client works.
  • A webhook endpoint that accepts HTTPS POST requests. For a first run, a temporary endpoint from a service such as webhook.site is enough to see deliveries arrive.

All requests in this guide use the sandbox base URL https://api-sandbox.grailpay.com/api/v3. Every call must be made over HTTPS — calls over HTTP fail. For the base URLs, API keys, and shared request conventions, see Environments.

The Payments API uses Bearer authentication. Send your API key in the Authorization header on every request:

Bearer Authentication
Authorization: Bearer {YOUR_API_KEY}

Your key has the form ID|TOKEN_VALUE, for example 12345|abcdefg.

Confirm the key works by fetching the authenticated account with GET /api/v3/me:

cURL
curl --location 'https://api-sandbox.grailpay.com/api/v3/me' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer {YOUR_API_KEY}'

Verify: A 200 response returns your account’s entity.type, entity.uuid, and entity.name. If you receive a 401, check the header format against Authentication — the response message names the problem, and request_id identifies the call if you contact support.

The merchant is the KYB-verified anchor of the flow, and in this walkthrough it is the payee. Create one with POST /api/v3/merchants. Onboarding collects a point of contact, the business details, its beneficial owners, and a bank account. For the complete request body, see Onboard a Merchant and the API reference.

When you add a bank account manually, use a sandbox test routing number so the account validates without reaching a real institution. Pair routing number 226078036 with any 12-digit account number:

Merchant bank account (manual)
{
"bank_account": {
"account_name": "Acme Operating",
"account_number": "123456789012",
"routing_number": "226078036",
"account_type": "checking"
}
}

Any beneficial-owner SSN you send in the sandbox must be a valid test value: it cannot begin with 000 or a value in the 900–999 range, cannot have 00 as its middle two digits, and cannot end in 0000.

Verify: A successful response returns the merchant’s uuid. Save it for use as the payee_uuid in Step 5.

The person is the other side of the payment, and in this walkthrough it is the payor whose bank account is debited. Create one with POST /api/v3/people. You must provide a bank account when creating a person; onboarding validates the account in real time with Account Intelligence.

Reuse the sandbox test routing number 226078036 with a 12-digit account number:

cURL
curl --location 'https://api-sandbox.grailpay.com/api/v3/people' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: afc24e55-8321-4172-8903-67e7ed80f07b' \
--header 'Authorization: Bearer {YOUR_API_KEY}' \
--data '{
"person": {
"first_name": "Will",
"last_name": "Clance",
"address": {
"line_1": "681 Avenue C",
"city": "Bayonne",
"state": "NJ",
"zip": "07002"
}
},
"bank_account": {
"account_name": "Will Clance Checking",
"account_number": "210987654321",
"routing_number": "226078036",
"account_type": "checking"
},
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}'

The Idempotency-Key header makes the request safe to retry without creating a duplicate person. Generate a fresh V4 UUID for each new request. See Idempotency for the full behavior. For the complete field list, see Onboard a Person and the API reference.

Verify: A successful response returns the person’s uuid and an actions.account_intelligence object confirming the validation that ran (shown abridged — the full object also includes a risk score and decisioning insights):

Account Intelligence result (abridged)
{
"actions": {
"account_intelligence": {
"version": "v3",
"name_match": true
}
}
}

Save the person’s uuid — it becomes the payor_uuid in Step 5. For the full result, see Account Intelligence.

Webhooks deliver event updates — compliance decisions, transaction progress, and payouts — to your endpoint in real time. Register one URL with POST /api/v3/webhooks. Every registered URL receives every event; your handler routes on the event field in the payload.

cURL
curl --location 'https://api-sandbox.grailpay.com/api/v3/webhooks' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {YOUR_API_KEY}' \
--data '{
"url": "https://your-endpoint.example.com/grailpay/webhooks"
}'

Verify: A successful response returns the registration’s uuid, the url you registered, and a secret. Store the secret — GrailPay signs every delivery with it, and you use it to verify the Signature header on incoming events. See Manage Webhooks for signature verification and delivery rules.

With both parties onboarded and the merchant approved, create the payment with POST /api/v3/transactions. The transaction debits the payer and credits the payee. Amounts are in cents, so 1000 is $10.00.

cURL
curl --location 'https://api-sandbox.grailpay.com/api/v3/transactions' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 9f1d2c7a-6b54-4e21-9a3c-7c2f1b0e44d8' \
--header 'Authorization: Bearer {YOUR_API_KEY}' \
--data '{
"payor_uuid": "{PERSON_UUID}",
"payee_uuid": "{MERCHANT_UUID}",
"amount": 1000
}'

Replace {PERSON_UUID} with the payer from Step 3 and {MERCHANT_UUID} with the payee from Step 2. For optional fields such as description, company_name, and payout modality, see Create a Transaction and the API reference.

To send the payment the other way, swap the two values: the merchant’s uuid as payor_uuid and the person’s as payee_uuid. Nothing else in the request changes; the merchant remains the KYB-verified anchor on either side.

Verify: A 201 response returns the new transaction with its uuid and an initial status of QUEUED. A repeated request with the same Idempotency-Key returns 409 instead of creating a second transaction.

Track the transaction as the ACH debit and payout move. You have two options:

  • Webhooks — your endpoint from Step 4 receives transaction.* and payout.* events as the state changes. This is the recommended approach for production integrations.
  • Polling — fetch the current state with GET /api/v3/transactions/{uuid}.
cURL
curl --location 'https://api-sandbox.grailpay.com/api/v3/transactions/{TRANSACTION_UUID}' \
--header 'Accept: application/json' \
--header 'Authorization: Bearer {YOUR_API_KEY}'

Verify: The transaction advances from QUEUED through capture states such as CAPTURE_PENDING and CAPTURE_ACH_PENDING. For the full lifecycle and the events fired at each stage, see Transaction States & Events.

You have moved a payment end to end in the sandbox. You authenticated your API key, onboarded a merchant and a person with validated bank accounts and used them as the two sides of a payment, registered a webhook, created a transaction, and followed it through its first states — the same sequence a production integration follows, without touching a real financial institution.

If you run into any issues, contact support@grailpay.com.

Maintained by EkLine