Appearance
Test your integration
Exercise every path your integration handles — success and failure — on staging, before you go live.
New to the API? Walk Get started first; this page assumes you have.
Requirements
| Requirement | Description |
|---|---|
| Staging credentials | Ask your Zay contact in your shared Slack channel |
| A phone and an inbox | For signing in as test users — any country's phone number works |
| A webhook endpoint | Your real handler, deployed somewhere public over HTTPS |
How staging differs from production
| Staging | Production | |
|---|---|---|
| Hosts | api-connect.zay.me, privy-stag.zay.me | api.zay.me, privy.zay.me |
| Identity verification | Sandbox — accepts any details, with a pass / fail toggle on screen | Real verification |
| Network | Base Sepolia | Base |
| Payout confirmation | Not live yet — every payout expires after 48 hours | Not live yet — every payout expires after 48 hours |
| Credentials | Separate | Separate |
See How payouts work.
Onboarding scenarios
Create a user, open their onboarding_url, and steer the flow:
| To test | Do this | You receive |
|---|---|---|
| A user who finishes | Pick United States, set the verification toggle to pass, then wait for the card | user.onboarding_started, user.kyc_passed, then user.onboarding_completed with wallet_address |
| Failed verification | Set the toggle to fail | user.onboarding_failed with a reason; the user stays onboarding |
| An unsupported country | Pick any country but the United States | No completion event. The user lands on a waitlist screen, and your callback_url gets status=residence_country_waitlisted |
| A user who stops halfway | Close the tab after the phone step, then open the same onboarding_url again | The user resumes where they stopped |
| The return to your product | Add a callback_url and finish the flow | A redirect to it with ?status=… — see Onboard a user |
Each user needs its own phone number and external_id, so keep a few numbers you can receive SMS on, or reuse a user who already finished.
Payout scenarios
| To test | Do this | You receive |
|---|---|---|
| An expired payout | Report a payout — any hash | payout.expired with reason deposit not confirmed within the verification window — after 48 hours |
| A safe retry | Send the same report twice with the same Idempotency-Key, within 30 seconds | The same payout back, once — see Report a payout |
| Validation | Report "amount": "0", or omit transaction_hash | 422 with the field in errors |
An expired payout takes 48 hours to arrive, and a received one cannot arrive until confirmation is live. To test your payout.expired and payout.received handlers sooner, feed them the example payloads from the Event catalog, signed the way Receive and verify webhooks describes.
Webhook scenarios
Use Trigger test delivery to send webhook_endpoint.test on demand:
| To test | Do this | What happens |
|---|---|---|
| Verification | Trigger a test delivery | Your handler verifies the signature and answers 2xx |
| Duplicates | Trigger it, answer 500 once, then 2xx | Zay retries with the same webhook-id; your handler processes it once |
| A forged event | Send your endpoint a request with a wrong webhook-signature | Your handler rejects it with 400 |
| An unknown event type | Send your endpoint a signed event with an invented type | Your handler ignores it and answers 2xx |
Don't answer 410 unless you mean it: it disables the endpoint at once. Re-enable it with Update webhook endpoint.
Request scenarios
| To test | Do this | You receive |
|---|---|---|
| A signing bug | Sign with the wrong key | 401 with string_to_sign_bytes — see Sign requests |
| Clock drift | Send a zay-timestamp 10 minutes old | 401 Request timestamp outside allowed drift of 5 minutes |
| Rate limits | Send more than 100 requests in a minute to one resource | 429 — see Handle errors and retries |
Check the result
You're ready for Go live when, on staging:
- a user finished onboarding and you received their
user.onboarding_completed; - a payout you reported was accepted, and your handlers process
payout.receivedandpayout.expired; - your handler verifies signatures and processes a retried event only once.
Related
- Go live — moving to production
- Handle errors and retries
- How payouts work — what is not live yet