Essentials
Webhooks
Octo notifies your backend of events by POSTing signed JSON to URLs you register. Today the main event is deposit.created, fired when a deposit confirms on-chain and is attributed to one of your addresses.
Register an endpoint
POST
/v1/wallets/:id/webhooksRequest
curl -X POST http://localhost:8080/v1/wallets/<WALLET_ID>/webhooks \
-H "authorization: Bearer octo_sk_test_ab12…" \
-H "content-type: application/json" \
-d '{ "url": "https://your.app/webhooks/octo" }'The response includes a signing secret, shown once. You use it to verify deliveries.
ℹ
The URL must be a public
http(s) endpoint. Loopback and private addresses are rejected.Event payload
POST to your URL
X-Octo-Signature: <hmac-sha256 hex>
Content-Type: application/json
{
"event": "deposit.created",
"data": {
"id": "1f2e…",
"wallet_id": "52775…",
"address_id": "8d22…",
"asset_code": "native",
"amount_stroops": 50000000,
"source_account": "GA…",
"stellar_tx_hash": "7f18…",
"memo_id": 7,
"status": "confirmed",
"attributed": true,
"metadata": { "plan": "pro" }
}
}The metadata is exactly what you attached when creating the address — use it to reconcile the deposit to your user.
Verify the signature
Each delivery includes an X-Octo-Signature header: the lowercase hex HMAC-SHA256 of the raw request body using your signing secret. Recompute it and compare in constant time before trusting the event.
Node.js
import crypto from "node:crypto";
function verify(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody) // the exact bytes received
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
app.post("/webhooks/octo", (req, res) => {
const sig = req.header("X-Octo-Signature");
if (!verify(req.rawBody, sig, process.env.OCTO_WEBHOOK_SECRET)) {
return res.status(401).end();
}
const { event, data } = JSON.parse(req.rawBody);
// credit data.metadata's user by data.amount_stroops …
res.status(200).end();
});⚠
Verify against the raw body bytes, not a re-serialized object — re-serializing can change the bytes and break the signature.
Delivery & retries
- Respond with a
2xxto acknowledge. - Non-2xx responses are retried with exponential backoff; every attempt is logged.
- Deposits are deduplicated on-chain, so design your handler to be idempotent.