Skip to main content

Webhooks

Most operations in the Prezent API are asynchronous — POST /api/v1/autogenerations returns a callback_id you then poll. Webhooks let you skip the polling: subscribe an HTTPS URL once, and Prezent will deliver a signed HTTPS POST every time a matching event occurs.

Use webhooks when you want push-style integration into your own backend (queue jobs into your worker, update a deal in your CRM, fan events out over your own broker). Keep polling for low-volume CLIs, scripts, or environments where you can't expose a public HTTPS endpoint.

Full request/response schemas for the subscription-management endpoints live in the interactive API Reference under the Webhooks tag.

At a glance

PropertyValue
ScopePer API key — each key manages its own subscriptions
TransportHTTPS POST, JSON body
SignatureX-Prezent-Signature: t=<unix>,v1=<hex> (HMAC-SHA256, Stripe-style)
Retries7 attempts over a 21-hour window
Auto-disable50 consecutive failures flips status to disabled
Manage via/api/v1/webhook-subscriptions REST resource
Max subscriptions per key25 (soft cap — contact us for more)

Authentication for management endpoints

All /api/v1/webhook-subscriptions/* endpoints require the same Bearer token used for the rest of the API:

Authorization: Bearer YOUR_API_KEY

See Getting Started → Authentication for details. Subscriptions are scoped to the API key that created them — a key can only list, read, modify, or delete its own subscriptions.

Event catalog

Each delivered payload's type is one of the codes below. Subscribe with events: ["*"] to receive everything, or list specific types to filter at the source.

Event typeFires when
autogeneration.completedAn AutoGenerator job finishes successfully.
autogeneration.failedAn AutoGenerator job ends in failure.
template_conversion.completedA template-conversion run finishes successfully.
template_conversion.failedA template-conversion run ends in failure.
webhook.testEmitted only by POST /api/v1/webhook-subscriptions/{id}/test.

New event types are strictly additive — we'll never repurpose an existing code. You can safely ignore unknown type values; we recommend logging them so we can flag a misconfiguration.

Envelope

Every delivered event shares the same top-level envelope. The event-specific fields live under data.

FieldTypeNotes
idstringUnique event id, e.g. evt_8c2a1f7e…. Stable across retries — use it (or the X-Prezent-Event header) to deduplicate.
typestringOne of the event types above.
api_versionstringCurrently "1.0".
created_atstringISO-8601 UTC timestamp of when the event occurred.
dataobjectEvent-specific payload (see below).

autogeneration.completed

Fires when an AutoGenerator job finishes successfully. The data object carries the job identifiers, the internal workflow name, and the opaque outputs produced by the run.

{
"id": "evt_8c2a1f7e6d3b4a5c9e0f8d7b6a5c4d3e",
"type": "autogeneration.completed",
"api_version": "1.0",
"created_at": "2026-05-25T10:05:42Z",
"data": {
"callback_id": "ag_5f7a9b1c8d2e4f5a",
"report_id": "rpt_2c9e0f8d7b6a5c4d",
"workflow_name": "sb",
"status": "success",
"outputs": { "...": "..." },
"execution_start": "2026-05-25T10:03:11Z",
"execution_end": "2026-05-25T10:05:40Z"
}
}
  • callback_id — the same id returned by POST /api/v1/autogenerations; correlate it with your original request.
  • report_id — the generated report's id.
  • workflow_name — the internal pipeline workflow that ran (e.g. sb). Treat this as an opaque string; new values may be added.
  • status"success" for this event type.
  • outputs — an opaque object describing the produced artifacts. Do not hardcode its inner shape.
  • execution_start / execution_end — ISO-8601 UTC timestamps bounding the run.

autogeneration.failed

Fires when an AutoGenerator job ends in failure. Same envelope, but the data object replaces outputs with error_log and sets status: "failed".

{
"id": "evt_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d",
"type": "autogeneration.failed",
"api_version": "1.0",
"created_at": "2026-05-25T10:06:10Z",
"data": {
"callback_id": "ag_5f7a9b1c8d2e4f5a",
"report_id": "rpt_2c9e0f8d7b6a5c4d",
"workflow_name": "sb",
"status": "failed",
"error_log": "...",
"execution_start": "2026-05-25T10:03:11Z",
"execution_end": "2026-05-25T10:06:08Z"
}
}
  • status"failed" for this event type.
  • error_log — diagnostic detail for the failure. Treat it as opaque and surface it to your operators rather than parsing it.

template_conversion.completed

Fires when a template-conversion run finishes successfully. The data object has the same shape as autogeneration.completed; only the type and the workflow_name differ.

{
"id": "evt_9f0e8d7c6b5a4d3c2b1a0f9e8d7c6b5a",
"type": "template_conversion.completed",
"api_version": "1.0",
"created_at": "2026-05-25T11:20:03Z",
"data": {
"callback_id": "tc_4d3c2b1a0f9e8d7c",
"report_id": "rpt_7c6b5a4d3c2b1a0f",
"workflow_name": "tc-api-workflow",
"status": "success",
"outputs": { "...": "..." },
"execution_start": "2026-05-25T11:18:40Z",
"execution_end": "2026-05-25T11:20:01Z"
}
}

template_conversion.failed

Fires when a template-conversion run ends in failure. Same shape as autogeneration.failed, carrying error_log and status: "failed".

{
"id": "evt_3c2b1a0f9e8d7c6b5a4d3c2b1a0f9e8d",
"type": "template_conversion.failed",
"api_version": "1.0",
"created_at": "2026-05-25T11:21:55Z",
"data": {
"callback_id": "tc_4d3c2b1a0f9e8d7c",
"report_id": "rpt_7c6b5a4d3c2b1a0f",
"workflow_name": "tc-api-workflow",
"status": "failed",
"error_log": "...",
"execution_start": "2026-05-25T11:18:40Z",
"execution_end": "2026-05-25T11:21:53Z"
}
}

webhook.test

Emitted only when you call POST /api/v1/webhook-subscriptions/{id}/test — never by real jobs. Use it to confirm your endpoint receives, verifies, and 2xx-acks a delivery before production traffic flows. Its data object is deliberately minimal:

{
"id": "evt_test_4f5a6b7c8d9e0f1a2b3c4d5e",
"type": "webhook.test",
"api_version": "1.0",
"created_at": "2026-05-25T09:59:00Z",
"data": {
"subscription_id": "whsub_3f7a9b1c8d2e4f5a6b7c8d9e0f1a2b3c",
"message": "This is a synthetic test event from the Prezent API."
}
}

The test event's id uses an evt_test_ prefix to make it obvious in logs that it did not originate from a real job. The /test endpoint is single-shot — it is delivered once with no retries and no dead-letter handling, and the response returns the subscriber's HTTP status and body verbatim.

Delivery headers

Every delivery — for all event types above — carries the same set of HTTP headers:

HeaderExampleMeaning
Content-Typeapplication/jsonBody is always JSON.
User-AgentPrezent-Webhooks/1.0Identifies Prezent's delivery agent.
X-Prezent-Eventevt_8c2a1f7e6d3b4a5c9e0f8d7b6a5c4d3eThe event id. Immutable across retries — deduplicate on this.
X-Prezent-Event-Typeautogeneration.completedThe event type, mirrored from the body.
X-Prezent-Signaturet=1716545142,v1=8d9a…c4fStripe-style HMAC-SHA256 signature. See Verify the signature.
X-Prezent-Deliverywhdl_4f5a6b7c8d9e0f1a2b3c4d5eA per-attempt delivery id (changes on each retry).
X-Prezent-Attempt1Attempt counter, starting at 1; increments on each retry.

Quickstart

1. Create a subscription

curl -X POST https://api.prezent.ai/api/v1/webhook-subscriptions \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/prezent",
"events": ["autogeneration.completed", "autogeneration.failed"],
"description": "Push completions into our deal-room worker"
}'

The response contains the HMAC secret, returned once:

{
"success": true,
"data": {
"id": "whsub_3f7a9b1c8d2e4f5a6b7c8d9e0f1a2b3c",
"url": "https://hooks.example.com/prezent",
"events": ["autogeneration.completed", "autogeneration.failed"],
"status": "active",
"secret": "whsec_4mZkV8t9oFp...truncated",
"secret_prefix": "whsec_",
"created_at": "2026-05-25T10:00:00Z",
"updated_at": "2026-05-25T10:00:00Z"
}
}

Store secret immediately — it will not be returned again. Reads only show the secret_prefix (first 6 chars). If you lose it, call POST /api/v1/webhook-subscriptions/{id}/rotate-secret.

2. Verify connectivity

curl -X POST https://api.prezent.ai/api/v1/webhook-subscriptions/whsub_.../test \
-H "Authorization: Bearer $PREZENT_API_KEY"

We immediately POST a synthetic webhook.test event to your endpoint and return the HTTP status + response body verbatim. Use this to confirm signing verification works before real traffic flows.

3. Receive events

Every delivery is a JSON POST shaped like:

{
"id": "evt_8c2a1f7e6d3b4a5c9e0f8d7b6a5c4d3e",
"type": "autogeneration.completed",
"api_version": "1.0",
"created_at": "2026-05-25T10:05:42Z",
"data": {
"callback_id": "ag_5f7a9b1c8d2e4f5a",
"report_id": "rpt_2c9e0f8d7b6a5c4d",
"status": "success",
"outputs": { "...": "..." }
}
}

with HTTP headers:

POST /prezent HTTP/1.1
Content-Type: application/json
User-Agent: Prezent-Webhooks/1.0
X-Prezent-Event: evt_8c2a1f7e6d3b4a5c9e0f8d7b6a5c4d3e
X-Prezent-Event-Type: autogeneration.completed
X-Prezent-Signature: t=1716545142,v1=8d9a...c4f
X-Prezent-Delivery: whdl_4f5a6b7c8d9e0f1a2b3c4d5e
X-Prezent-Attempt: 1

4. Verify the signature

The signature header is Stripe-compatible:

X-Prezent-Signature: t=<unix_timestamp>,v1=<hex_hmac_sha256>

The signed string is "{t}.{raw_body}" (decimal timestamp, a dot, then the raw request body, byte-for-byte). Compute HMAC-SHA256(secret, signed_string), hex-encode, and compare to v1. Use a constant-time comparison.

We strongly recommend rejecting requests whose t is more than 5 minutes off your local clock — this prevents replay of an intercepted payload.

# This is a one-liner for ad-hoc verification only.
# Production code should use a real HTTP server framework.
SECRET="whsec_..."
RAW='{"id":"evt_...","type":"autogeneration.completed",...}'
TS=1716545142
EXPECTED=$(printf '%s.%s' "$TS" "$RAW" \
| openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
echo "expected v1=$EXPECTED"

Delivery semantics

  • Acceptable responses. Any 2xx status within 10 seconds counts as a successful delivery. We do not parse your response body — it is captured for the dashboard only.
  • At-least-once. Network blips can cause the same event to be delivered more than once. Each delivery carries an immutable X-Prezent-Event header and id in the body — deduplicate on those.
  • Order is not guaranteed. A retry can land after the next event. Use created_at if you need a wall-clock anchor.
  • Retries. A failed delivery is retried at +1m, +5m, +30m, +2h, +6h, and +18h. After 21 hours, the event is dropped to a dead-letter queue and surfaced on the dashboard.
  • Auto-disable. After 50 consecutive failures we flip the subscription to status: "disabled" and stop attempting delivery. Patch back to status: "active" once you've fixed the endpoint.
  • Idempotency on your side. Subscribers must be idempotent: a successful response on a duplicate is the cheapest way to handle retries.

URL requirements

For security and reliability we enforce the following at create + update time:

  • https:// only. http:// URLs are rejected with 400 INVALID_URL_SCHEME.
  • The hostname must resolve to a public IP. Private (RFC 1918, 127.0.0.0/8, 169.254.0.0/16, IPv6 ULA, link-local) addresses are rejected with 400 INVALID_URL_PRIVATE_HOST. This protects you and us from SSRF.
  • Ports 22, 23, 25, 3306, 5432, 6379, 9200, 11211, 27017 are rejected with 400 INVALID_URL_PORT.

Managing subscriptions

MethodPathWhat it does
POST/api/v1/webhook-subscriptionsCreate a new subscription. Returns the HMAC secret once.
GET/api/v1/webhook-subscriptionsList your active subscriptions (paginated).
GET/api/v1/webhook-subscriptions/{id}Read a single subscription. The secret is never returned — only secret_prefix.
PATCH/api/v1/webhook-subscriptions/{id}Update url, events, description, or status.
DELETE/api/v1/webhook-subscriptions/{id}Soft-delete the subscription.
POST/api/v1/webhook-subscriptions/{id}/rotate-secretIssue a fresh HMAC secret. Old secret is invalidated immediately.
POST/api/v1/webhook-subscriptions/{id}/testSend a synthetic webhook.test event to your URL and return the response verbatim.

See the API Reference for full request/response schemas.

Pausing a subscription

A common operational pattern is pausing deliveries during maintenance:

curl -X PATCH https://api.prezent.ai/api/v1/webhook-subscriptions/whsub_.../  \
-H "Authorization: Bearer $PREZENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "disabled" }'

Events that occur while disabled are not retroactively delivered once you re-enable. For replay/backfill, fall back to GET /api/v1/autogenerations/{callback_id} polling for the gap.

Rotating the secret

curl -X POST https://api.prezent.ai/api/v1/webhook-subscriptions/whsub_.../rotate-secret \
-H "Authorization: Bearer $PREZENT_API_KEY"

The new secret is returned once in the response. The old secret stops verifying as soon as the rotation completes — there is no grace window. If you need overlap, stand up a second subscription on a distinct path, switch over, then delete the old one.

Errors

All endpoints on this page return the standard error envelope. Webhook-specific codes:

  • 400 INVALID_URL_SCHEMEurl is not https://.
  • 400 INVALID_URL_PRIVATE_HOSTurl resolves to a private IP.
  • 400 INVALID_URL_PORTurl uses a disallowed port.
  • 400 INVALID_EVENT_TYPEevents contains an unknown type. Use ["*"] or names from the catalog.
  • 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND — id not found, or owned by a different API key.

See the full catalog in Developer Guide → Error codes.

See also