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 reaches status=success.
autogeneration.failedAn AutoGenerator job reaches status=failed.
template_conversion.completedA template-conversion run reaches status=success.
template_conversion.failedA template-conversion run reaches status=failed.
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.

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