Skip to main content
A webhook is a signed HTTPS POST that 0xinsider sends to your server every time an event you subscribed to fires. This page gets you a verified endpoint, a signature check that runs before you parse the body, and a way to catch up after downtime. Every webhook route needs an API key on an active Pro subscription. Without one the API answers 402 with error.reason subscription_inactive.

Choose event types

Webhook events returns this catalog. Put the types you want in event_types when you create the endpoint.
  • live_sports_updated sends one pulse per game, and at most one every 20 seconds per game. A game going live or final is sent straight away. The game clock moving on its own never sends one. See Live game pulses.
  • suspicious_trade_flagged and insider_radar_flag_raised are one event under two spellings. Subscribe with either name; listing both on one endpoint stores one. Your endpoint receives every delivery under the spelling it registered, in the x-0xinsider-event-type header and in the payload’s type, so an endpoint created before September 23, 2026 keeps receiving insider_radar_flag_raised.
  • large_trades_inserted (and its older spelling whale_trades_inserted) carries a count of new large trades, not the trades themselves. Read the trades from Large trades.
  • large_trade_inserted_v2 carries one trade per delivery. Set trade_filters when you create or update the endpoint to require a condition_id, wallet, minimum min_grade, or minimum min_size_usd; all supplied filters must match. grade is captured at publication and may be null, which never passes a minimum-grade filter. The delivery id stays stable across retries.
  • large_trades_inserted and whale_trades_inserted, and trader_synced and whale_trader_synced, are each one event under two spellings, like the suspicious-trade pair below: an endpoint receives the spelling it registered.
  • The four export_job_* events go only to endpoints owned by the account that submitted the export.
  • When a Pro subscription lapses, every queued delivery of a Pro-only type moves to dead_letter with last_error set to delivery owner is no longer authorized for this event.

Create and verify the endpoint

1

Create it

The response shows signing_secret (whsec_...) and verification.token (whv_...) once each. Store both now, because no later read returns either one. The endpoint receives nothing until step 2.
2

Verify it within 24 hours

The call sends a signed webhook.verification challenge to your URL. Your server has to answer 2xx within 10 seconds before the endpoint becomes active. Holding the token is not enough on its own.
The challenge body is {"type":"webhook.verification","token":"<verification_token>","webhook_id":<id>}. It is signed the same way a delivery is, and it carries x-0xinsider-timestamp, x-0xinsider-signature, and x-0xinsider-event-type webhook.verification. It carries none of the event, delivery, or idempotency headers listed below. Two answers mean the endpoint did not activate:
  • 422 with error.param url: your server answered outside 2xx, took longer than 10 seconds, or is not reachable from the public internet.
  • 400 with error.param verification_token: the token is wrong or has expired. Delete the endpoint and create it again.

What a delivery carries

A delivery is a POST with a JSON body and the User-Agent 0xinsider-webhooks/1.0. Answer with any 2xx within 15 seconds, then do your processing after you have replied.

Live game pulses

live_sports_updated sends one pulse per game. A pulse goes out when a material field moves since that game’s last pulse: scores, status, period, live, or ended. The game clock moving on its own never sends one, and the clock still rides every pulse. Two rules bound the volume. An ordinary change waits out 20 seconds since that game’s last pulse: the pulse goes out on the first frame the provider sends after that. A game going live or final ignores the wait, and so does a game’s first pulse, so you never miss a start or a final. A game that stops sending frames stops sending pulses. Live games send a frame about every 20 seconds, and the frame that ends a game is exempt from the wait, so a finished game always sends its last pulse. A change that arrives inside the 20 seconds is not dropped. The next pulse lists it in changed and its body carries the current state, so you see every field that moved, but not every value it passed through.

Check the signature

The signature covers the timestamp and the raw body, joined by a dot.
  • Sign the raw request bytes. Parsing the JSON and serializing it again changes the HMAC.
  • Use the whole whsec_... string as the key, prefix included.
  • Reject a request whose x-0xinsider-timestamp is more than 300 seconds from your own clock.
  • Split x-0xinsider-signature on commas and accept the request when any candidate matches. A staged rotation sends two candidates for an hour.
  • Compare each candidate in constant time.
  • Deduplicate on x-0xinsider-event-id before you act on the event.

Retries

An attempt fails when your server answers outside 2xx, the connection errors, or the request runs past the 15-second deadline. A delivery gets 8 attempts in total. After the eighth failure its status becomes dead_letter and 0xinsider stops. Each failed attempt doubles the ceiling on the next wait: 60 seconds after the first failure, then 120, 240, 480, 960, 1,920, and 3,600 seconds. The delivery worker wakes every 10 seconds, so a wait can run up to 10 seconds longer than its ceiling. The wait you get inside that ceiling depends on how the attempt failed. Each row below names the value the delivery log reports in retry_schedule_reason. Retry-After is read as a number of seconds or as an HTTP-date. A missing, malformed, or already-past value is ignored, and the delivery falls back to its ordinary wait. Webhook deliveries reports status, last_error, next_attempt_at, and retry_schedule_reason on each row, and keeps rows for 7 days. Two more reasons appear there: manual_redelivery after Redeliver a webhook delivery, and configuration_changed when an edit to the endpoint released an attempt that had not been sent. Both next_attempt_at and retry_schedule_reason are null while an attempt is in flight and once the delivery is delivered or dead_letter. One delivery can therefore keep retrying for up to 7,380 seconds, a little over 2 hours. The endpoint itself is usually disabled long before that:
  • failure_count on the endpoint counts consecutive failed attempts across all of its deliveries. Any success resets it to 0.
  • At 8 consecutive failures the endpoint’s status becomes disabled and 0xinsider emails the account owner.
  • A busy endpoint that goes down is therefore disabled within minutes, not hours.
Disabling an endpoint moves every queued delivery to dead_letter. Re-enabling it with {"enabled": true} on Update a webhook does not resend them. Requeue each one with Redeliver a webhook delivery, or catch up with the replay below.

Replay after downtime

Event replay is the only durable catch-up, and it covers large_trades_inserted (whale_trades_inserted) alone. Every other type is delivered, retried, and then dropped.
  1. Store next_cursor from every replay response.
  2. After the outage, call /api/v1/events/feed/since?cursor=<the cursor you stored>.
  3. Process each event in data, then store the response’s new next_cursor.
  4. Repeat while has_more is true.

Rotate the signing secret

Rotate the secret when it reaches git, a log, a screenshot, or anyone who should not have it. There are two ways to do it.

Staged rotation, with a 1-hour overlap

Use this one unless you are racing a leak. Both secrets verify while you deploy the new one, so no delivery is rejected on the way.
1

Prepare the new secret

Prepare a staged webhook secret returns the new signing_secret and sets secret_rotation.status to pending. Deliveries keep using the current secret. Deploy the new one to every receiver before you move on.
2

Activate it

Activate a staged webhook secret promotes the prepared secret and returns it once more. secret_rotation.status becomes overlap, and secret_rotation.overlap_expires_at is the instant the overlap ends, 1 hour later. Until then x-0xinsider-signature carries two comma-separated v1=<hex> candidates: the new secret’s signature first, the old secret’s second.
3

Retire the old secret

Retire a staged webhook secret ends the overlap as soon as every receiver accepts the new secret. secret_rotation.status goes back to idle and deliveries carry one signature again. If you never call it, the overlap ends on its own at overlap_expires_at.
All three calls take an Idempotency-Key. Repeating prepare with the same key while a secret is still pending returns that same secret, so a lost response costs you nothing. Changing the endpoint’s url on Update a webhook discards a prepared or overlapping secret.

Immediate rotation, after a leak

Rotate a webhook secret replaces the secret in a single call.
The old secret stops working when the call returns, and there is no overlap. Deploy the new signing_secret to every receiver immediately: a delivery your server rejects is retried about a minute later and counts toward the 8 consecutive failures that disable the endpoint. A 409 with error.reason webhook_delivery_in_progress means a delivery is in flight, so send the request again once it finishes.

What it does not do

  • Deliver to a laptop or a private network. The URL has to be public HTTPS on port 443. To read events from a machine with no public address, open the Stream instead.
  • Carry the trades themselves. large_trades_inserted says how many new large trades there are, not which ones.
  • Replay any event type other than large_trades_inserted (whale_trades_inserted).
  • Filter by market, size, or grade. Subscribe to the type and filter in your own code; Alert bot shows that loop.
  • Return signing_secret a second time. Webhook and Webhooks never carry it, so rotate the secret if you lose it.