AI92 je v živo - naša globalna predčasna predstavitev je v teku: celotna platforma je odprta po vsem svetu prvih 30 dni.
Meni dokumentacije

Ta razvijalska dokumentacija je trenutno na voljo samo v angleščini. Koda in obnašanje API-ja sta enaka v vseh regijah.

Webhooks

Webhooks push events to your server the moment they happen — a contact gets verified, a campaign reply arrives, a visitor is identified, your credit balance runs low — so you don't have to poll. You register an HTTPS endpoint, subscribe it to event types, and AI92 POSTs a signed JSON payload to it on each matching event.

Setting up an endpoint

Register an endpoint and the event types it should receive:

bash
curl -X POST https://api.ai92.ai/v1/webhooks/endpoints \
  -H "Authorization: Bearer ai92_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/ai92",
    "events": ["contact.verified", "campaign.reply", "credits.low"]
  }'

The response returns a signing secret — shown once. Store it securely; you'll use it to verify every delivery.

Event catalog

EventFires when
contact.verifiedAn email verification resolves to a definitive verdict.
campaign.sentA campaign message is dispatched.
campaign.replyA recipient replies to a campaign.
campaign.openA recipient opens a message.
campaign.clickA recipient clicks a tracked link.
campaign.bounceA message bounces.
visitor.identifiedAn anonymous visitor is identified (intent).
review.newA new review lands on a connected profile.
seo.alertAn SEO alert triggers (ranking change, issue detected).
credits.lowYour balance crosses the low-balance threshold.
key.expiringAn API key is approaching expiry.
account.inactivity_warningThe account is nearing the inactivity threshold.
account.read_onlyThe account has been moved to read-only mode.
account.reactivatedA read-only account is reactivated.
sla.violationAn SLA threshold is breached.

Only registered event types are accepted. Subscribing to an event name that is not in the catalog above returns 422 UNKNOWN_EVENT_TYPE, and the endpoint is not created — one bad name fails the whole request. In particular, async job and scan completions are not delivered via webhook: there is no job.completed or scan.completed event. Poll those results instead — GET /v1/jobs/{job_id} for f_*/strat-int-* jobs, GET /v1/seo/audit/{scan_id} and GET /v1/contacts/bulk/{scan_id} for the UUID-scan_id producers (see Async Jobs).

The payload

Every delivery is a JSON body in this shape:

json
{
  "id": "evt_8f2c1e9a-0b44-4c0d-9b2f-1a6d3e8f0011",
  "type": "contact.verified",
  "created": "2026-06-21T10:42:00Z",
  "account_id": "9b1c...",
  "trace_id": "7f3c...",
  "data": {
    "email": "[email protected]",
    "status": "valid"
  }
}

And these headers:

HeaderMeaning
X-AI92-SignatureHMAC signature: t=<timestamp>,v1=<hex>
X-AI92-Event-TypeThe event type, e.g. contact.verified
Content-Typeapplication/json
User-Agentmod923-webhook/<version>

Verifying the signature

Always verify. Without it, anyone who learns your URL could forge events. AI92 signs each delivery with HMAC-SHA256 over "{timestamp}.{raw_body}" using your endpoint's signing secret.

The X-AI92-Signature header looks like:

t=1718966520,v1=5257a869e7 ... 9f2c

To verify, step by step:

  1. Read the raw body exactly as received — do not re-serialize it. (AI92 signs a canonical, sorted-key JSON; your framework's re-encode would change the bytes and break verification. Verify against the raw bytes on the wire.)
  2. Parse X-AI92-Signature into its t (timestamp) and v1 (signature) parts.
  3. Recompute HMAC_SHA256(secret, f"{t}.{raw_body}") as hex.
  4. Compare the recomputed value with v1 using a constant-time comparison.
  5. Reject stale timestamps (e.g. t older than 5 minutes) to block replay attacks.
python
import hashlib, hmac, time

def verify_ai92_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance=300) -> bool:
    parts = dict(p.split("=", 1) for p in signature_header.split(","))
    ts, sig = parts.get("t"), parts.get("v1")
    if not ts or not sig:
        return False
    if abs(time.time() - int(ts)) > tolerance:      # replay protection
        return False
    signed = f"{ts}.{raw_body.decode('utf-8')}".encode()
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)       # constant-time
javascript
import crypto from "node:crypto";

function verifyAi92Webhook(rawBody, signatureHeader, secret, tolerance = 300) {
  const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=")));
  const { t, v1 } = parts;
  if (!t || !v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > tolerance) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
python
# Flask handler — note request.get_data() for the RAW body
from flask import Flask, request, abort
app = Flask(__name__)

@app.post("/webhooks/ai92")
def handle():
    raw = request.get_data()  # raw bytes, not request.json
    if not verify_ai92_webhook(raw, request.headers.get("X-AI92-Signature", ""), SECRET):
        abort(400)
    event = request.get_json()
    # enqueue and return fast — see below
    enqueue(event)
    return "", 200

Retries and the DLQ

If your endpoint doesn't return a 2xx, AI92 retries with a back-off schedule:

  • Up to 5 attempts.
  • Retry intervals (seconds after each failure): 120 → 600 → 3,600 → 14,400 → 64,800 (2 min, 10 min, 1 h, 4 h, 18 h).
  • Each delivery attempt has a 10-second timeout.
  • After the final failed attempt, the delivery moves to a dead-letter queue (DLQ), retained for 30 days, where you can inspect and replay it.

A 2xx at any attempt marks the delivery complete and stops retries.

Best practices

  • Return 2xx fast. Acknowledge within the 10-second timeout, then do the real work asynchronously. Verify the signature, enqueue the event, and return 200 immediately — don't run slow logic inline.
  • Make your consumer idempotent. Retries (and rare duplicate deliveries) mean you may see the same id more than once. Dedupe on event.id and process each exactly once.
  • Verify every payload with the HMAC check above, and reject stale timestamps.
  • Subscribe narrowly. Only request the event types you handle.
  • Watch the DLQ. A delivery in the DLQ means your endpoint was down or rejecting — replay it once you've fixed the cause.