Skip to main content

Webhooks

Subscribe to events and we'll POST a signed JSON body to your URL when they happen — no polling needed.

Registering a webhook

curl -X POST https://api.userevaluation.com/v1/webhooks \
-H "Authorization: Bearer ue_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.example.com/hooks/ue",
"events": ["project.created", "test.published", "engage.session.completed"],
"description": "production handler"
}'

The response includes a one-time secret field starting with whsec_. Save it — it's how you'll verify incoming requests. We can't show it again.

Event types

EventWhen
project.createdA project is created (UI or API)
project.updatedProject name/description changed
project.deletedProject deleted
test.publishedAn engage test went Draft → Live
test.response.submittedA participant submits a test response
file.transcribedTranscription completed for a file (video, audio, doc)
report.generatedReport-generation job succeeded
chat.completedChat job succeeded (assistant turn ready)
engage.session.completedA live/AI interview call finished
tag.createdA new tag was created in a project

Subscribe to as many as you want with one webhook, or split across several.

Request shape

We POST JSON with these headers:

Content-Type: application/json
X-UE-Event: project.created
X-UE-Event-Id: 9a3f7c2d-1715520600000
X-UE-Signature: t=1715520600, v1=8c4e…
User-Agent: UE-Webhooks/1.0

And this body:

{
"id": "9a3f7c2d-1715520600000",
"type": "project.created",
"created_at": "2026-05-12T14:30:00.000Z",
"data": { ...resource payload... }
}

Always return a 2xx status as fast as you can — we time out after 5 seconds.

Verifying signatures

The X-UE-Signature header is t=<unix_timestamp>, v1=<hex>. To verify:

  1. Take the timestamp t.
  2. Take the raw request body (don't re-serialize — bytes matter).
  3. Compute HMAC_SHA256(secret, "<t>.<rawBody>") in hex.
  4. Compare to the v1= value with a constant-time comparison.

Optionally, reject signatures older than ~5 minutes to limit replay risk.

Node example

import crypto from "node:crypto";

function verify(req, secret) {
const header = req.headers["x-ue-signature"];
const m = /t=(\d+),\s*v1=([0-9a-f]+)/.exec(header || "");
if (!m) return false;
const [, ts, sig] = m;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${req.rawBody}`) // raw, not parsed
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Python example

import hmac, hashlib

def verify(headers, raw_body, secret):
sig = headers.get("x-ue-signature", "")
t, v1 = dict(p.strip().split("=", 1) for p in sig.split(",")).get(...)
expected = hmac.new(secret.encode(), f"{t}.{raw_body}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)

Retries and auto-disable

If your endpoint returns non-2xx (or times out), we retry with exponential backoff:

AttemptDelay before this attempt
1(immediate)
210s
31m
45m
530m
61h

After 24 hours of continuous failures, we automatically disable the webhook. We'll email the user that created it. Re-enable by deleting and recreating the subscription once your endpoint is healthy.

Listing past deliveries

curl "https://api.userevaluation.com/v1/webhooks/<id>/deliveries" \
-H "Authorization: Bearer ue_live_..."

Returns the last 30 days of attempts with status, response code, and error.

Best practices

  • Idempotency on your side too: we may deliver the same event more than once on retry. Use X-UE-Event-Id to dedupe.
  • Verify before parsing: check the signature with the raw bytes BEFORE you JSON-parse. Otherwise you can't catch a tampered body that happens to be valid JSON.
  • Respond fast: queue the event, return 200, then process. 5 seconds is the timeout.
  • One handler per event class: simpler than dispatching by type inside a single handler.

Webhooks vs. Zapier triggers

Both fire on roughly the same events, but they're independent paths — pick one per event you care about.

  • Public webhooks (this doc) are fast, signed, retried, owned by your workspace. Best for production integrations you're maintaining.
  • Zapier triggers are best for no-code workflows where you don't run your own server.

If you subscribe to the same event in both, expect both to fire — there's no automatic dedupe between the two systems. Either pick one path per event, or have your handlers idempotent on X-UE-Event-Id (webhooks) and the Zapier action's deduplication key.