PLANNED, not shipped · No Intelligence webhook event exists today

Webhooks for events.

Subscribe to Orbyt Intelligence events, with signed, reliable delivery and local-dev tunneling.

API docsCLI tunneling

Why webhooks.

Webhooks deliver Orbyt Intelligence events to your server in near-real-time. Instead of polling the API for fresh data on a schedule, your application subscribes to the events you care about and reacts when they fire. The result is fresher data with less load on both sides.

The three most common use cases. Cache invalidation: when data.refreshed fires, your application invalidates its cached comp bands so the next request hits the fresh dataset. Alerting: when velocity.changed fires for a critical role, your comp team gets a Slack notification. Auditing: when lineage.updated fires, your compliance team logs the new provenance trail.

The webhook surface is designed to feel like Stripe's. Signature format is compatible (HMAC-SHA256 with a timestamp prefix). Event types follow a stable noun.verb naming. Idempotency keys appear on every delivery. Retry policy is exponential backoff with a 24-hour terminal window. Deliveries persist in the dashboard for 30 days so you can replay them if your endpoint was down.

Event catalog.

data.refreshed

Fires when a methodology version increments and one or more engines republish data. Payload includes the engines updated and the new methodology version.

Example: AI Role Taxonomy republished: 2026.1 to 2026.2. Roles added and deprecations carried in the payload.
data.corrected

Fires when a previously-published data point is corrected (typically after a methodology fix or source-data restatement). Payload includes the old value, new value, and the reason.

Example: data_point_id ai-engineer:san-francisco:median_base:2026-Q1 corrected from $245K to $238K. Reason: SOC mapping fix.
data.retracted

Fires when a data point is retracted (rare: only when the underlying source data is found to be unreliable). The data point becomes unreachable; lookups return 410 Gone.

Example: Company signals for company:example-private:hiring-intent:2026-Q1 retracted. Source: misclassified posting batch.
velocity.changed

Fires when a role × city combination crosses a velocity threshold (e.g., enters Hot or exits Hot). Payload includes the role, the city, the four velocity metrics, and the new + previous status.

Example: llm-systems-engineer × san-francisco: status changed Warm → Hot. Posting volume +34% WoW. Time-to-fill 62 days.
lineage.updated

Fires when a data point's provenance trail changes: for example, when a new source is added to the reconciliation set. Useful for compliance teams that audit citations.

Example: ai-engineer:san-francisco:median_base: new H-1B LCA batch added to reconciliation set. Methodology version unchanged.

Signature verification.

Every webhook delivery carries an Orbyt-Signature header. The signature is computed as:

Orbyt-Signature: t=1747006334,v1=5257a869e7ecebeda32... # Where: # t = unix timestamp of delivery # v1 = HMAC-SHA256 hex digest of t + "." + raw_body # signed with your webhook signing secret

Verify by computing the HMAC on your side and comparing to the header. Reject deliveries older than 5 minutes: Stripe-compatible defense against replay attacks.

Node.js / TypeScript
import crypto from "node:crypto"; const TOLERANCE_SECONDS = 300; export function verifyOrbytSignature(rawBody: string, header: string, secret: string): boolean { const parts = Object.fromEntries( header.split(",").map((p) => p.split("=") as [string, string]) ); const t = Number(parts.t); const v1 = parts.v1; if (!t || !v1) return false; if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false; const expected = crypto .createHmac("sha256", secret) .update(t + "." + rawBody) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)); }
Python
import hmac, hashlib, time TOLERANCE_SECONDS = 300 def verify_orbyt_signature(raw_body: bytes, header: str, secret: str) -> bool: parts = dict(p.split("=") for p in header.split(",")) t = int(parts.get("t", 0)) v1 = parts.get("v1", "") if not t or not v1: return False if abs(time.time() - t) > TOLERANCE_SECONDS: return False expected = hmac.new( secret.encode("utf-8"), f"{t}.".encode() + raw_body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(v1, expected)
Ruby
require "openssl" TOLERANCE_SECONDS = 300 def verify_orbyt_signature(raw_body, header, secret) parts = header.split(",").map { |p| p.split("=", 2) }.to_h t = parts["t"].to_i v1 = parts["v1"] return false unless t > 0 && v1 return false if (Time.now.to_i - t).abs > TOLERANCE_SECONDS expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{t}.#{raw_body}") Rack::Utils.secure_compare(v1, expected) end
Go
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "strconv" "strings" "time" ) const ToleranceSeconds = 300 func VerifyOrbytSignature(rawBody []byte, header, secret string) bool { parts := map[string]string{} for _, p := range strings.Split(header, ",") { kv := strings.SplitN(p, "=", 2) if len(kv) == 2 { parts[kv[0]] = kv[1] } } t, err := strconv.ParseInt(parts["t"], 10, 64) if err != nil || parts["v1"] == "" { return false } if abs(time.Now().Unix()-t) > ToleranceSeconds { return false } mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(strconv.FormatInt(t, 10) + "." + string(rawBody))) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(parts["v1"]), []byte(expected)) } func abs(x int64) int64 { if x < 0 { return -x }; return x }
PHP
function verify_orbyt_signature(string $rawBody, string $header, string $secret): bool { $tolerance = 300; $parts = []; foreach (explode(",", $header) as $p) { $kv = explode("=", $p, 2); if (count($kv) === 2) $parts[$kv[0]] = $kv[1]; } $t = (int) ($parts["t"] ?? 0); $v1 = $parts["v1"] ?? ""; if (!$t || !$v1) return false; if (abs(time() - $t) > $tolerance) return false; $expected = hash_hmac("sha256", $t . "." . $rawBody, $secret); return hash_equals($v1, $expected); }

Retry policy and delivery guarantees.

If your endpoint returns anything other than a 2xx response, we retry with exponential backoff. The schedule is fixed and visible:

Attempt 1: immediate Attempt 2: 30 seconds later Attempt 3: 5 minutes later Attempt 4: 30 minutes later Attempt 5: 2 hours later Attempt 6: 6 hours later Attempt 7: 24 hours later After 24 hours of failures, the delivery is marked failed. Failed deliveries are retained for 30 days at /api/v1/webhooks/deliveries?status=failed.

Your endpoint should be idempotent: every delivery carries an Orbyt-Idempotency-Key header. Record the key in a dedupe table and short-circuit subsequent deliveries with the same key. The same key is repeated across every retry attempt for the same event, so dedup is straightforward.

Reasonable handler shape: return 2xx as soon as you have durably recorded the event (database write, queue enqueue). Do not block on downstream processing: that adds latency and increases your retry surface. Process asynchronously after the 2xx.

Event versioning.

Each event payload includes an event_version field. As of September 2026 every event type ships at version 1. New fields are added in a backwards-compatible way without bumping the version. Breaking changes (extremely rare) increment the version and the customer must opt-in via dashboard or API to migrate. The previous version continues to fire for at least 6 months after a successor version ships.

{ "id": "evt_a4f9b21c8e7d3f01", "type": "data.refreshed", "event_version": 1, "occurred_at": "2026-05-11T18:32:14Z", "request": { "request_id": "req_b5g0c32d9f8e4g12" }, "data": { "methodology_version": "2026.2", "previous_methodology_version": "2026.1", "engines_updated": [ "ai-role-taxonomy" ], "summary": { "roles_added": 6, "roles_deprecated": 1 } } }

Local development with CLI tunneling.

Testing webhook handlers against production traffic is brittle. The Orbyt CLI ships a secure tunnel that forwards production webhook deliveries from your Orbyt account to your local server, preserving signature headers so signature verification works end-to-end.

$ orbyt webhooks listen --forward-to=http://localhost:3000/api/webhooks/orbyt ✓ Tunnel established: https://tunnel.orbytlabs.ai/u/a4f9b21c ✓ Forwarding to http://localhost:3000/api/webhooks/orbyt ✓ Ctrl-C to disconnect [2026-05-11T18:32:14Z] data.refreshed (req_a4f9b21c8e7d3f01) → 200 OK (42ms) [2026-05-11T18:35:02Z] data.corrected (req_b5g0c32d9f8e4g12) → 200 OK (28ms)

See the CLI page for installation and full command reference.

Pricing and access.

Webhook subscriptions are planned for the Ultra tier. No Orbyt Intelligence webhook event exists today. The design is up to 50 webhook endpoints per account, unlimited deliveries within the rate plan, and 30-day retention of failed deliveries for replay. Full pricing at /orbyt-intelligence/pricing.

See also.

Last updated September 2026. Webhooks v1 stable. Signature format compatible with Stripe's pattern. 24-hour retry window with 30-day failed-delivery retention.