Event Subscriptions

Event subscriptions let Plextera push product events to your webhook endpoint. Use them when your integration should react to completed, failed, or rejected processing without polling.

When to use events

PatternRecommended for
Polling onlySimple backend integrations, local testing, or flows where delayed processing is acceptable.
Events onlyProduction integrations that need push notifications and lower API traffic.
Events plus pollingRobust production integrations. Use events for the normal path and polling as a fallback or reconciliation pass.

A common production pattern is: subscribe to terminal events, process webhook deliveries immediately, and run a periodic polling reconciliation for resources that have not completed after an expected time window.

Available events

EventDelivered whenPayload
document-insights.extraction.completedA document extraction reaches COMPLETED.Full extraction with output.
document-insights.extraction.failedA document extraction reaches FAILED.Extraction state with error.
document-insights.extraction.rejectedA document extraction reaches REJECTED.Extraction state with error.
workflow.run.completedA workflow run reaches COMPLETED.Full workflow run with step outputs.
workflow.run.failedA workflow run reaches FAILED.Workflow run state with error.

Setup

1

Create an event subscription

Call POST /event-subscriptions with:

  • endpointUrl - the HTTPS URL Plextera should POST events to.
  • eventTypes - one or more event types to subscribe to.
  • signingSecret - a secret used to sign deliveries.
  • filters - optional filters such as a workflow ID or Document Insights operation.
$curl -X POST https://api.plextera.com/api/public/v1/event-subscriptions \
> -H "Authorization: api-key YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "endpointUrl": "https://example.com/webhooks/plextera",
> "eventTypes": [
> "document-insights.extraction.completed",
> "document-insights.extraction.failed",
> "document-insights.extraction.rejected"
> ],
> "signingSecret": "whsec_your_secret",
> "filters": {
> "documentInsightsOperation": "extract"
> }
> }'
2

Implement your webhook endpoint

Your endpoint must:

  • Accept POST requests with a JSON body.
  • Read the raw request body for signature verification.
  • Return a 2xx status after the event is accepted.
  • Handle duplicate deliveries safely.
3

Verify the signature

Verify the X-Plextera-Signature header before trusting the event payload.

Event payload model

Every event uses a common envelope:

1{
2 "eventId": "evt_01JY7M9QWBWCPMZK5QJ7RSE9P4",
3 "eventType": "document-insights.extraction.completed",
4 "occurredAt": "2026-04-07T10:22:00Z",
5 "apiVersion": "v1",
6 "data": { ... }
7}

The data field contains the event-specific payload. See the Event Reference for complete schemas and examples.

Delivery headers

Every webhook delivery includes:

HeaderDescription
X-Plextera-Delivery-IdUnique ID for this delivery attempt.
X-Plextera-Event-IdID of the event, stable across retry attempts.
X-Plextera-Event-TypeEvent type string.
X-Plextera-Event-Occurred-AtISO 8601 timestamp when the event occurred.
X-Plextera-Api-VersionAPI version, for example v1.
X-Plextera-SignatureHMAC-SHA256 signature for payload verification.

Verifying signatures

Each delivery is signed using the signingSecret you provided when creating the subscription. This confirms the delivery came from Plextera and that the payload was not modified.

Signature format:

X-Plextera-Signature: t=<unix timestamp>,v1=<hex hmac-sha256>

Verification steps:

  1. Extract t and v1 from the header.
  2. Construct the signed payload: <t>.<raw request body>.
  3. Compute HMAC-SHA256 using your signingSecret.
  4. Compare the computed signature with v1 using a constant-time comparison.
  5. Optionally reject old timestamps for replay protection.
1import hashlib
2import hmac
3import time
4
5
6def verify_signature(
7 payload: bytes,
8 header: str,
9 secret: str,
10 max_age_seconds: int = 300,
11) -> bool:
12 parts = dict(p.split("=", 1) for p in header.split(","))
13 timestamp = int(parts["t"])
14 signature = parts["v1"]
15
16 if abs(time.time() - timestamp) > max_age_seconds:
17 return False
18
19 signed = f"{timestamp}.".encode() + payload
20 expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
21 return hmac.compare_digest(expected, signature)

The signing secret is write-only. Plextera never returns it in API responses. Store it securely and rotate it by updating the subscription with a new signingSecret.

Retry and idempotency

  • Any 2xx response marks the delivery as successful.
  • Non-2xx responses trigger retries with exponential backoff.
  • The same eventId may be delivered more than once.
  • Your webhook handler should be idempotent. Store processed eventId values or use your own deduplication key.
  • If a document is reprocessed after a terminal state, a later terminal state can produce a new event.