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.
Prerequisites
Section titled “Prerequisites”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.
Step 1: Authenticate
Section titled “Step 1: Authenticate”The Payments API uses Bearer authentication. Send your API key in the Authorization header on every request:
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 --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.
Step 2: Onboard the merchant
Section titled “Step 2: Onboard the merchant”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:
{ "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.
Step 3: Onboard the person
Section titled “Step 3: Onboard the person”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 --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):
{ "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.
Step 4: Register a webhook
Section titled “Step 4: Register a webhook”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 --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.
Step 5: Create a transaction
Section titled “Step 5: Create a transaction”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 --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.
Step 6: Follow the transaction
Section titled “Step 6: Follow the transaction”Track the transaction as the ACH debit and payout move. You have two options:
- Webhooks — your endpoint from Step 4 receives
transaction.*andpayout.*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 --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.
Summary
Section titled “Summary”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.
Next steps
Section titled “Next steps”- Understand the money movement — read the Payments Platform Overview for ACH entry types, payment speeds, returns, and settlement timing.
- Handle every event — map the events your integration must react to in Webhook Events.
- Score risk before you pay — add Account Intelligence and Transaction Intelligence to screen accounts and transactions.
- Go live — when your integration is tested, switch to your production API key and base URL. Review Environments first.
Questions?
Section titled “Questions?”If you run into any issues, contact support@grailpay.com.
Maintained by EkLine

