Skip to content
AIRO

Webhooks

Skip polling: register an HTTPS endpoint and AIRO calls it when an optimization finishes.

Register an endpoint

curl -X POST https://ai-route-optimization.com/v1/webhooks \
  -H "Authorization: Bearer $AIRO_API_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://erp.example.com/hooks/airo"}'

The response includes the signing secret (whsec_airo_…) once. Store it. You can register up to five endpoints; list them with GET /v1/webhooks and remove one with DELETE /v1/webhooks/{id}.

  • URLs must be https and public. Private, loopback and link-local addresses are refused, and redirects are not followed.
  • Events: optimization.completed, optimization.failed, optimization.cancelled.

Payload

{
  "id": "evt_6f1c…",
  "type": "optimization.completed",
  "created": "2026-10-14T13:02:19Z",
  "data": {
    "optimization_id": "0d1f0c3e-5b64-4c0e-9a7a-2f6d3d5c9a10",
    "status": "completed",
    "error_code": null,
    "error_message": null,
    "links": {
      "self": "https://ai-route-optimization.com/v1/optimizations/0d1f0c3e-…",
      "result": "https://ai-route-optimization.com/v1/optimizations/0d1f0c3e-…/result"
    }
  }
}

Payloads contain ids and links only, never customer data. Fetch the result with your API key.

Verify the signature

Every request carries an AIRO-Signature header: t=<unix seconds>,v1=<hex>. The signature is HMAC-SHA256 over '<t>.<raw body>' with your endpoint secret. Verify it on the raw bytes and reject requests older than five minutes.

Node.js
import crypto from "node:crypto";

// Express: use express.raw({ type: "application/json" }) so you hold the exact bytes.
function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > toleranceSeconds) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.`).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Python
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(","))
    if abs(time.time() - int(parts["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Delivery and retries

  • Respond with any 2xx within 5 seconds. Anything else counts as a failure.
  • Each event is attempted up to three times in quick succession. Webhooks complement polling: if you miss one, GET the optimization.
  • An endpoint that fails 20 times in a row is disabled. Check last_status and failure_count in GET /v1/webhooks, fix the receiver, then register it again.
  • Events can repeat. Use the event id, or the optimization id and status, to de-duplicate.