Raw API

Set up and test-fire a webhook

Webhooks tell you when a buy or sell settles, so you never poll for it. Setting one up is one field and a button; the part worth reading carefully is how to verify the signature.

6 min read · 3 screenshots

Open Webhooks in the Build section.

The Webhooks page of the OroPocket developer dashboard, showing an endpoint card with a webhook URL field and an enabled checkbox, above a table of recent deliveries.
Endpoint configuration on top, delivery history below.
  1. Add your endpoint

    Paste an https URL that is reachable from the public internet. Private and internal addresses are rejected — not just localhost and RFC 1918 ranges, but anything that resolves to one, checked at connection time rather than only when you save.

    The webhook endpoint card in the OroPocket dashboard, showing the webhook URL field, an Enabled checkbox, and Save, Rotate secret, Send test event and Remove buttons.
    Enabled is a pause switch — it stops deliveries without discarding your URL or secret.
  2. Copy the signing secret

    Saving mints a whsec_… secret and shows it once. As with API keys, we store a hash — if you lose it, rotate to get a new one and update your receiver. A URL can never exist without a secret, so no delivery ever goes out unsigned.

  3. Send a test event

    Send test event fires a test.ping immediately, which is the cheapest way to prove your endpoint is reachable, returns 2xx, and validates the signature correctly — before a real trade depends on it.

    The recent deliveries table on the OroPocket webhooks page, showing rows with timestamp, event type, delivery status badge, HTTP code, attempt count, and Show error and Replay actions.
    Delivered, pending and failed, with the attempt count and the response we got.
  4. Verify the signature

    This is the step integrations get wrong. We sign the timestamp and the raw body joined by a dot — not the body alone:

    const expected = "sha256=" + crypto
      .createHmac("sha256", process.env.ORO_WEBHOOK_SECRET)
      .update(`${req.headers["x-oropocket-timestamp"]}.${rawBody}`)
      .digest("hex");
    
    // Compare in constant time, and reject old timestamps.
    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-oropocket-signature"]))) {
      return res.status(400).end();
    }
    Use the exact bytes you received. Parsing the JSON and re-serialising it changes whitespace and key order, and the signature will never match.

What gets delivered

EventFires when
buy.completedA buy is fulfilled and the metal is credited.
sell.completedA sell settles to the user's INR wallet.
sip.createdA recurring plan is created.
sip.installmentAn installment of a recurring plan executes.
test.pingYou pressed Send test event.

Users are identified only by user_code. Dedupe on event_id, which stays the same across retries of the same event.

Retries, and replaying by hand

We wait 10 seconds for a response. Any 2xx acknowledges; anything else is retried up to five times, backing off at 0.5, 2, 10, 60 and 360 minutes. The queue is durable, so retries survive a restart on our side.

When a delivery has exhausted its attempts, Show error on the row displays the last response we got, and Replay sends it again once you have fixed the receiver.

The webhook URL and secret are one setting per partner, not one per mode. Changing them in Test mode changes them for Live too. Test and Live deliveries are listed separately, but they are going to the same place — so point a receiver at somewhere that can tell them apart, using the mode field in the payload.

Custom headers

If your receiver authenticates with a static header, the page has a Custom headers card for up to ten name/value pairs. Anything beginning x-oropocket- is rejected, so nothing can shadow the signature headers.

Full payload shapes and the retry schedule are in the webhooks reference.