Skip to main content

Webhook Integration

Emerald delivers a signed HTTP POST to your registered URL whenever a deposit or withdrawal is attributed to your partnerId. Delivery is HMAC-signed so you can verify authenticity, and retried on failure.

Registering a Webhook

Your endpoint must be https and resolve to a public host (loopback, private, and link-local addresses are rejected). Emerald issues you a signing secret (whsec_…) out of band, alongside your API key.

curl -X POST https://api.emeraldvaults.io/api/v1/partner/webhooks \
-H "x-api-key: sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-server.com/webhooks/emerald"}'

Response:

{
"ok": true,
"data": { "webhookUrl": "https://your-server.com/webhooks/emerald" }
}

Calling this endpoint again overwrites the existing webhook URL.

Events

Two event types are delivered:

EventTrigger
deposit.referralA deposit attributed to your partnerId was indexed
withdrawal.referralA withdrawal request attributed to your partnerId was indexed

Payload

Each delivery is a JSON envelope. Numeric on-chain amounts are native-unit decimal strings.

{
"id": "f3c1a2b4-...-uuid",
"version": 1,
"type": "deposit.referral",
"createdAt": "2026-06-11T12:00:00.000Z",
"partnerId": 7,
"data": {
"vaultAddress": "0xdc55...",
"userAddress": "0xabc...",
"assets": "1000000000",
"shares": "999000000000000000000",
"txHash": "0x...",
"blockNumber": "24840900",
"logIndex": 3,
"timestamp": "2026-06-11T11:59:48.000Z"
}
}

For withdrawal.referral, data carries shares, assetsEstimate, and requestIndex instead of assets / shares.

Headers

HeaderMeaning
X-Indigo-Event-IdUUID — also payload.id. Retries reuse it; use it to deduplicate.
X-Indigo-Event-Typedeposit.referral / withdrawal.referral
X-Indigo-TimestampUnix seconds. Reject deliveries older than ~5 min (replay defense).
X-Indigo-Signaturesha256=<hex> — HMAC-SHA256 of `${timestamp}.${rawBody}` keyed with your signing secret.

Verifying the signature

Sign over the raw request body bytes, not a re-serialized object.

import crypto from "node:crypto";

function verify(req, secret) {
const ts = req.headers["x-indigo-timestamp"];
const sig = req.headers["x-indigo-signature"]; // "sha256=…"

// 1) freshness — reject stale timestamps
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

// 2) recompute over the raw body
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(`${ts}.${req.rawBody}`).digest("hex");

// 3) constant-time compare
return (
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
);
}

Delivery & retries

  • Respond 2xx within 10 seconds to acknowledge.
  • A non-2xx response or timeout triggers up to 3 retries with backoff (1s / 5s / 15s). The same X-Indigo-Event-Id is reused on every attempt.
  • Redirects are not followed — point us at your final endpoint.

Best Practices

  1. Deduplicate on X-Indigo-Event-Id — an event may be delivered more than once.
  2. Verify every payload with the signature check above before trusting it.
  3. Process asynchronously — enqueue the payload and return 2xx immediately.
  4. Rotate your signing secret with Emerald if it is ever exposed.