Reference

Webhooks

Get notified the moment something happens — no polling. Every delivery is signed and retried.

Set your webhook URL and reveal your signing secret from the Webhooks page. We POST a JSON event to your URL and retry with backoff until you return a 2xx.

Events

FieldTypeDescription
buy.completedeventoptionalA buy was fulfilled and the asset credited.
sell.completedeventoptionalA sell settled into the user's INR wallet.
sip.createdeventoptionalA recurring plan (SIP) mandate was created.
sip.installmenteventoptionalA SIP installment executed.
test.pingeventoptionalSent when you use “Send test event” in the dashboard.

Payload

Every delivery has the same envelope. event_id is stable across retries — deduplicate on it. Sandbox deliveries carry "mode": "sandbox".

{
  "event": "buy.completed",
  "event_id": "evt_9f2c1a7b3d",
  "created_at": "2026-08-08T09:12:44.000Z",
  "mode": "live",
  "data": {
    "user_code": "usr_xxx",
    "asset_type": "gold",
    "amount_inr": 1000,
    "quantity_grams": 0.1234,
    "transaction_id": "…",
    "status": "completed"
  }
}

Headers

FieldTypeDescription
X-OroPocket-Signaturestringoptionalsha256=<hex HMAC>. See below — signed over `{timestamp}.{rawBody}`, NOT the body alone.
X-OroPocket-TimestampstringoptionalUnix seconds. Part of the signed payload; reject old timestamps to stop replays.
X-OroPocket-EventstringoptionalThe event name, e.g. buy.completed.
X-OroPocket-Event-IdstringoptionalSame value as event_id in the body — use it to dedupe.

Any static credential headers you need (e.g. x-access-key) can be attached to every delivery including retries — ask support to configure them. OroPocket-set headers can never be overridden.

Verify the signature

The signature is computed over `${timestamp}.${rawBody}` — the timestamp, a literal dot, then the raw request body. Hashing the body on its own will fail every time. Use the raw bytes, before any JSON parsing or re-serialisation.
import crypto from "crypto";

// rawBody must be the raw string/Buffer, e.g. express.raw({type:"application/json"})
function verify(rawBody, headers, secret) {
  const signature = headers["x-oropocket-signature"] || "";
  const timestamp = headers["x-oropocket-timestamp"] || "";

  // Reject replays: ignore anything older than 5 minutes.
  const age = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!timestamp || age > 300) return false;

  const expected =
    "sha256=" +
    crypto
      .createHmac("sha256", secret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Retries

We make one immediate attempt with a 10 second timeout, then retry failures up to 5 attempts total. Any 2xx acknowledges the delivery; non-2xx responses and timeouts are retried on this backoff:

FieldTypeDescription
After attempt 130 secondsoptionalFirst retry.
After attempt 22 minutesoptional
After attempt 310 minutesoptional
After attempt 460 minutesoptional
After attempt 56 hoursoptionalFinal attempt; then the delivery is marked failed.

The retry queue lives in the database, so retries survive restarts. Return a 2xx as soon as you've persisted the event and do slow work asynchronously — otherwise the 10s timeout will cause duplicate deliveries.

Test-fire an event and watch deliveries (with replay) from the Webhooks page — no need to wait for a real transaction.