Skip to content
Docs

Šis puslapis dar neišverstas, todėl rodomas angliškai.

Webhooks

ORYVOX sends signed webhooks to endpoints you add in the dashboard under

ORYVOX sends signed webhooks to endpoints you add in the dashboard under Core > Observability > Incidents & alerts (/app/core/monitoring), in the webhooks list. The URL must be a public https:// address; internal and private network addresses are refused. Each endpoint gets its own signing secret (whsec_...), shown once when you add it. An endpoint receives every public event, or only the types you pick. Send test delivers a test.ping (always delivered, whatever the filter).

Events

TypeWhen
security.alertA warning or critical security event: blocked or masked secrets, blocked personal data, denied IPs, scope denials, rate-limit hits, new key IPs, budget stops, sign-in and policy events. At most once per type every 15 minutes.
incident.openedAn alert rule or the automatic watch opened an incident (error rate, latency, provider trouble...).
incident.resolvedThat incident is resolved.
budget.warningA project's daily or monthly spend reached its warning level (budget_warn_at, default 80%).
budget.exceededA project's spend reached 100% of its budget.
credits.lowThe credit balance fell below your low-balance threshold.
credits.exhaustedThe balance reached zero; prepaid requests now fail with 402 insufficient_credits.
invoice.createdA monthly invoice was issued.
invoice.paidAn invoice was paid.
invoice.failedA payment failed.
test.pingSent by Send test.

Payload

{
  "id": "evt_k2j4...",
  "type": "budget.warning",
  "version": 1,
  "created": 1790000000,
  "data": {"project": "Support bot", "project_id": 12, "period": "month", "budget": 500.0, "spent": 412.37}
}

version is the event schema version. data depends on the type; budget amounts are in charged USD.

Signature

Every request has an Oryvox-Signature header:

Oryvox-Signature: t=1790000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t is the Unix time the request was signed.
  • v1 is the hex HMAC-SHA256 of "{t}.{raw_body}" keyed with the endpoint secret.

To verify: recompute the HMAC over the raw body, compare in constant time, and reject timestamps older than 5 minutes (replay protection). The header carries one v1 value; the SDK verifiers also accept several (any match passes).

The SDKs do all of this:

import Oryvox from '@oryvox/sdk';

app.post('/webhooks/oryvox', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = Oryvox.webhooks.verify(req.body, req.header('Oryvox-Signature'), process.env.ORYVOX_WEBHOOK_SECRET!);
  } catch {
    return res.sendStatus(400);
  }
  handle(event); // event.id, event.type, event.version, event.created, event.data
  res.sendStatus(200);
});
from oryvox import Oryvox, WebhookVerificationError

try:
    event = Oryvox.webhooks.verify(raw_body, headers["Oryvox-Signature"], WEBHOOK_SECRET)
except WebhookVerificationError:
    return 400

The optional last argument sets the tolerance in seconds (default 300).

Manual verification (any language):

import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = parts["t"]
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(t)) <= tolerance

Delivery and retries

  • Respond with any 2xx within 10 seconds. Do slow work asynchronously.
  • Anything else (another status, a timeout, a connection error) is retried, up to 8 attempts in total, with exponential backoff: 30 s, 2 min, 8 min, 32 min, about 2 h, then 6 h (the cap), so the last attempt is about 15 hours after the first.
  • After the last failed attempt the delivery is marked failed. The webhooks list shows each endpoint's last status and delivery time.
  • Delivery is at least once: the same event can arrive more than once, and events can arrive out of order. Deduplicate by event.id and use created to order them.
Ar šis puslapis buvo naudingas?