Gyvar docs

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 | base64

2. 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"}'
Response
{
  "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_minor for arithmetic and amount for display.
  • Confirm you treat an unknown details.code as 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.
deposit.success
{
  "event": "deposit.success",
  "event_id": "evt_...",
  "created_at": "2026-09-01T10:14:52Z",
  "data": {
    "amount": "25.00",
    "amount_minor": "25000000",
    "rail": "base"
  }
}