Skip to main content
This guide walks you through the full flow for accepting crypto deposits: creating a custodial account, generating wallet addresses per chain, registering webhooks, and crediting user accounts when deposits confirm.
Test this entire flow against the Sandbox at https://crypto-sandbox.yativo.com/api/ before going live. Sandbox deposits are simulated — no real funds are used.

Overview

The deposit flow has four components:
  1. Account — a logical container for assets belonging to one user or entity
  2. Wallet addresses — per-chain addresses derived from the account
  3. Webhooks — real-time notifications when deposits are detected and confirmed
  4. Business logic — your code credits the user after confirming the deposit
1

Create an account

Each user or entity in your system maps to a Yativo account. Create one at onboarding time and store the returned accountId.
curl -X POST https://crypto-api.yativo.com/api/accounts/create-account \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
  -H "Content-Type: application/json" \
  -d '{
    "label": "alice_account",
    "metadata": {
      "userId": "usr_01HYZPQR3NMVK8F2GXB7T9D4CE",
      "email": "alice@example.com"
    }
  }'
Response:
{
  "accountId": "acc_01J2K9MNPQ3R4S5T6U7V8W9X0Y",
  "label": "alice_account",
  "status": "active",
  "metadata": {
    "userId": "usr_01HYZPQR3NMVK8F2GXB7T9D4CE",
    "email": "alice@example.com"
  },
  "createdAt": "2026-03-26T10:00:00Z"
}
Store accountId in your database alongside the user record. You will reference it for every wallet and transaction operation.
2

Create wallet addresses per chain

For each blockchain you want to support, add an asset to the account. This returns a deposit address on that chain.
# Ethereum (ERC-20 / ETH)
curl -X POST https://crypto-api.yativo.com/api/assets/add-asset \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
  -H "Content-Type: application/json" \
  -d '{
    "accountId": "acc_01J2K9MNPQ3R4S5T6U7V8W9X0Y",
    "chain": "ethereum",
    "asset": "USDC"
  }'
Response (Ethereum example):
{
  "assetId": "ast_01J2KBMNPQ7R4S5T6U7V8W9XEth",
  "accountId": "acc_01J2K9MNPQ3R4S5T6U7V8W9X0Y",
  "chain": "ethereum",
  "asset": "USDC",
  "address": "0x742d35Cc6634C0532925a3b8D4C9D5b9a1f4e8Bd",
  "balance": "0.000000",
  "createdAt": "2026-03-26T10:01:00Z"
}
3

Register webhooks for deposit events

Register a webhook endpoint to receive deposit.detected (immediate, unconfirmed) and deposit.confirmed (safe to credit) events.
curl -X POST https://crypto-api.yativo.com/api/webhook/create \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3JfMDFIWVo..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.yourapp.com/webhooks/yativo",
    "events": ["deposit.detected", "deposit.confirmed"],
    "description": "Crypto deposit notifications"
  }'
Response:
{
  "webhookId": "wh_01J2KCMNPQ3R4S5T6U7VWEBHK",
  "url": "https://api.yourapp.com/webhooks/yativo",
  "events": ["deposit.detected", "deposit.confirmed"],
  "status": "active",
  "secret": "whsec_a8f3c2e1d4b7f9e2c5a3b6d8f1e4c7a2b5d8e1f4",
  "createdAt": "2026-03-26T10:02:00Z"
}
Store secret securely (e.g., in an environment variable). It is only returned once and is used to verify webhook signatures. See Step 6 for verification details.
4

Display wallet addresses to users

Fetch the wallet addresses for an account and present them in your UI. Users send crypto to these addresses.
// In your backend — return wallet addresses to your frontend
app.get("/api/deposit-addresses/:userId", async (req, res) => {
  const user = await db.users.findById(req.params.userId);

  const assets = await client.assets.listByAccount(user.yativoAccountId);

  const addresses = assets.map((a) => ({
    chain: a.chain,
    asset: a.asset,
    address: a.address,
    network: a.networkLabel, // "Ethereum Mainnet", "Polygon", etc.
  }));

  res.json({ addresses });
});
Show a QR code for each address in your UI — most crypto wallets can scan them directly.
5

Handle webhook events and credit user accounts

When a deposit confirms, your webhook handler receives a deposit.confirmed event. Verify the signature (next step), then credit the user.Here is a complete Express.js webhook handler:
TypeScript (Express webhook handler)
import express from "express";
import crypto from "crypto";

const app = express();

// Use raw body for signature verification
app.use(
  "/webhooks/yativo",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    // 1. Verify signature immediately
    const signature = req.headers["x-webhook-signature"] as string;
    const secret = process.env.YATIVO_WEBHOOK_SECRET!;
    const expectedSig = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    if (
      !signature ||
      !crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSig)
      )
    ) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    // 2. Parse event
    const event = JSON.parse(req.body.toString());

    // 3. Return 200 immediately — process async
    res.sendStatus(200);

    // 4. Process asynchronously
    setImmediate(async () => {
      try {
        await processEvent(event);
      } catch (err) {
        console.error("Failed to process event", event.eventId, err);
      }
    });
  }
);

async function processEvent(event: any) {
  // Idempotency: skip already-processed events
  const alreadyProcessed = await db.events.exists(event.eventId);
  if (alreadyProcessed) return;

  switch (event.type) {
    case "deposit.detected":
      // Optionally notify user of pending deposit — don't credit yet
      await notifyUserPendingDeposit(event.data);
      break;

    case "deposit.confirmed":
      const { accountId, amount, asset, chain, txHash } = event.data;

      // Look up the user by accountId
      const user = await db.users.findByYativoAccountId(accountId);
      if (!user) {
        console.warn("No user found for accountId", accountId);
        return;
      }

      // Credit the user
      await db.balances.credit(user.id, asset, amount);
      await db.transactions.record({
        userId: user.id,
        type: "deposit",
        asset,
        chain,
        amount,
        txHash,
        yativoEventId: event.eventId,
      });

      await notifyUserDepositConfirmed(user, amount, asset);
      break;
  }

  // Mark event as processed
  await db.events.markProcessed(event.eventId);
}
6

Verify webhook signatures

All webhook requests from Yativo include an X-Webhook-Signature header containing an HMAC-SHA256 signature of the raw request body.
function verifyWebhookSignature(
  rawBody: Buffer,
  signatureHeader: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // Use timing-safe comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected)
  );
}

Webhook Payload: deposit.confirmed

{
  "eventId": "evt_01J2KF3MNPQ7R4S5T6U7VDPST1",
  "type": "deposit.confirmed",
  "createdAt": "2026-03-26T10:15:32Z",
  "data": {
    "depositId": "dep_01J2KF3MNPQ7R4S5T6U7VDEP42",
    "accountId": "acc_01J2K9MNPQ3R4S5T6U7V8W9X0Y",
    "assetId": "ast_01J2KBMNPQ7R4S5T6U7V8W9XEth",
    "chain": "ethereum",
    "asset": "USDC",
    "amount": "250.000000",
    "address": "0x742d35Cc6634C0532925a3b8D4C9D5b9a1f4e8Bd",
    "txHash": "0x4a7d3f2e1c8b5a9e2f4d7c3b6a1e8d2f5c4b3a7e1d9f2c5b8a4e3d7c1b6a2f",
    "blockNumber": 19847201,
    "confirmations": 12,
    "networkFee": "0.001823",
    "metadata": {
      "userId": "usr_01HYZPQR3NMVK8F2GXB7T9D4CE"
    }
  }
}

Next Steps

  • See Webhook Integration for the full webhook reference including all event types and retry policy.
  • Use Send Crypto to move funds back out on behalf of users.