Quickstart
From nothing to a sandbox address and a webhook in about ten minutes.
Everything here runs against sandbox. Sandbox and live are the same host and the same code; the key you sign with decides which universe you land in.
1. Get a keypair and a key id
Generate a keypair and upload the public half in the dashboard under your project's API
keys. You get back a key id beginning gyv_k_sandbox_. Nothing has to be enabled first -
keys and sandbox are self-serve, in live too. See Access.
We never hold your private key, which means we also cannot reissue it: rotation is register-a-second-key, then revoke the first. See Signing for the full scheme and examples in more languages.
The IP allowlist is mandatory
A key will not authenticate from an address you have not listed. There is no permissive
default - if your first call returns ip_not_allowed, that is what happened, and the
error says so precisely rather than hiding behind a generic failure.
# macOS: /usr/bin/openssl is LibreSSL and answers "Algorithm ed25519 not found".
# brew install openssl@3, then run $(brew --prefix openssl@3)/bin/openssl.
openssl genpkey -algorithm ed25519 -out gyvar.pem
openssl pkey -in gyvar.pem -pubout # upload this
# your signing key. Already base64 - do not encode it again.
grep -v -- ----- gyvar.pem | base64 -d | tail -c 32 | base642. Create a receive address
An address is scoped to a chain, never to an asset.
reference is required - your identifier for whatever this address is for. It is also
the idempotency key: call it again with the same reference and you get the same address
back. That is the whole idempotency story for this endpoint.
There is no way to omit it. An address with no reference is your project's standing address for that chain, and that one is created in the dashboard - it is the one a person reads off the Wallet screen, and it should not have two places that mint it.
201 means an address was minted. 200 means you already had one for that reference and got it back. Both are success - the distinction is there so you can log which happened instead of trusting us about it.
assets is a list for a reason
base accepts USDC. ethereum accepts USDT and USDC on the same address. Do not
persist a single asset alongside an address - on a multi-asset chain the other one
eventually lands on it.
curl -sS -X POST https://api.gyvar.com/v1/addresses \
-H "X-Gyvar-Key: $GYVAR_KEY_ID" \
-H "X-Gyvar-Timestamp: $TS" \
-H "X-Gyvar-Nonce: $NONCE" \
-H "X-Gyvar-Signature: $SIG" \
-H "Content-Type: application/json" \
-d '{"chain":"base","reference":"customer_8842"}'{
"data": {
"id": "0d6c2f3a-1b4e-4c9a-9f2d-7a1e5c8b3d60",
"chain": "base",
"address": "0x...",
"assets": ["USDC"],
"reference": "customer_8842",
"created_at": "2026-09-01T10:04:11Z"
}
}3. Send it something, then receive the webhook
Send funds to the address on that chain. When the deposit confirms, Gyvar credits your
project's balance and emits deposit.success.
Point a URL at your project's webhook settings and verify the signature on every delivery.
Deduplicate on event_id from the verified body, never the header. See
Webhooks.
The faucet is not this step
The sandbox faucet on the Wallet screen credits your balance directly, the way a
confirmed deposit would have. It sends nothing to an address, so it emits no
deposit.success - use it to get a balance to spend from, not to test your webhook
handler. See Sandbox.
Going live
Swap the key. Same host, same payload shapes, real money. Before you do:
- Confirm your production egress IPs are on the live key's allowlist.
- Confirm you read
amount_minorfor arithmetic andamountfor display. - Confirm you treat an unknown
details.codeas a failure, not as success. - Re-test anything on bitcoin, lightning, tron or bsc. Sandbox cannot serve those four, so whatever you built for them has not actually run yet.
{
"event": "deposit.success",
"event_id": "evt_...",
"created_at": "2026-09-01T10:14:52Z",
"data": {
"amount": "25.00",
"amount_minor": "25000000",
"rail": "base"
}
}