Event Subscriptions

Receive webhook push notifications when processing reaches a terminal state

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 status check.

A common production pattern is: subscribe to terminal events, process webhook deliveries immediately, and periodically poll 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, or is closed in Plextera Studio (CLOSED).Workflow run state with error.

Closing a run in Plextera Studio is treated as a failure outcome for event delivery: subscribers of workflow.run.failed receive an event whose run status is CLOSED. Check the status field in the payload if your handler needs to distinguish the two.

Setup

1

Create an event subscription

Call POST /event-subscriptions with:

  • name - an optional display name for the subscription (maximum 128 characters).
  • endpointUrl - the HTTPS URL Plextera should POST events to (maximum 2,048 characters; credentials and fragments are not allowed).
  • eventTypes - one or more event types to subscribe to (maximum 16).
  • filters - optional filters for workflow events, for example a specific workflow ID (maximum 256 characters).

Plextera generates the signing secret. The 201 Created response returns it once together with the new active subscription. Store the value securely while handling the response and configure it in your webhook receiver.

Each workspace can have up to 50 event subscriptions. Delete an unused subscription before creating another one after reaching this limit. Create subscription returns 409 CONFLICT when the quota is already exhausted.

$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 '{
> "name": "Document extraction events",
> "endpointUrl": "https://example.com/webhooks/plextera",
> "eventTypes": [
> "document-insights.extraction.completed",
> "document-insights.extraction.failed",
> "document-insights.extraction.rejected"
> ]
> }'
201 Created
1{
2 "id": "sub_01JY7N4MT3JQW4TB7B8R5K2E6A",
3 "name": "Document extraction events",
4 "status": "active",
5 "endpointUrl": "https://example.com/webhooks/plextera",
6 "eventTypes": [
7 "document-insights.extraction.completed",
8 "document-insights.extraction.failed",
9 "document-insights.extraction.rejected"
10 ],
11 "createdAt": "2026-07-23T10:05:04Z",
12 "updatedAt": "2026-07-23T10:05:04Z",
13 "signingSecret": "whsec_Q3E2w8rQ0qH8w0yLq3bN5JWd4mY1uWz22H0Qw0Y9G5A"
14}

Document Insights subscriptions rarely need filters - the event types already select what is delivered. The response omits filters when none are configured.

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 within 15 seconds - acknowledge first, then process the event asynchronously. Slow responses time out and count as failed deliveries.
  • 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 "extractionId": "69654f0bc073ef404baec649"
8 }
9}

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-IdID of this subscription delivery, stable across retry attempts.
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 server-generated signingSecret returned when the subscription is created. 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)

Public list, get, and update responses never contain the signing secret, and the Public API has no reveal endpoint. Store the value from the successful create response securely. Authorized dashboard users can explicitly reveal the current secret if needed.

Retry and idempotency

  • 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.

Automatic retry schedule

The first attempt is immediate. A non-2xx response, connection error, or timeout then follows this schedule:

AttemptDelay after previous attemptApproximate time from first attempt
1ImmediateT+0
21 minuteT+1m
32 minutesT+3m
44 minutesT+7m
58 minutesT+15m
616 minutesT+31m

The maximum of six attempts includes the initial attempt. After the sixth failed attempt, the delivery becomes failed. The actual timestamps can be slightly later under load or during infrastructure recovery.

Events and deliveries

Plextera keeps four related records:

  • An event is one immutable product occurrence and contains the exact webhook payload.
  • A subscription defines which events should be sent and to which endpoint.
  • A delivery represents one event sent to one subscription.
  • An attempt is one HTTP request made for a delivery.

An event is recorded even when no active subscription matches it. One event can have several deliveries when several subscriptions match or when someone manually resends it.

Inspect events

Use GET /events to see events for the workspace, whether or not they were delivered. Results are newest first by default. Filter by eventType, subscriptionId, and the inclusive event-record createdAt range with from and to, or use sort=asc to read oldest first. When subscriptionId is present, the list contains only events with a delivery to that subscription and each event’s deliverySummary is scoped to that subscription. Manual resends do not duplicate the event in this list.

$curl "https://api.plextera.com/api/public/v1/events?eventType=document-insights.extraction.completed&from=2026-04-01T00:00:00Z&to=2026-04-30T23:59:59Z" \
> -H "Authorization: api-key YOUR_API_KEY"

Each item includes deliverySummary with counts for pending, retrying, delivered, and failed. Use GET /events/{eventId} to load the exact event payload. For Document Insights events, document.contentUrl is minted when the event is created and remains valid for up to five days. Retries and manual resends use the same payload and URL. If the URL has expired, call GET /document-insights/extractions/{extractionId} to obtain a fresh one.

To open event history for one subscription:

$curl "https://api.plextera.com/api/public/v1/events?subscriptionId=sub_..." \
> -H "Authorization: api-key YOUR_API_KEY"

Inspect deliveries for an event

Use GET /events/{eventId}/deliveries to see every delivery of one event across subscriptions and manual resends. Results are newest first by default. Filter by status (pending, retrying, delivered, failed), subscriptionId, and the inclusive createdAt range with from and to.

$curl "https://api.plextera.com/api/public/v1/events/evt_.../deliveries?status=failed&sort=asc" \
> -H "Authorization: api-key YOUR_API_KEY"

Each delivery identifies its subscriptionId and endpointUrl. It also shows its status, attemptCount, the last HTTP responseStatusCode, the last error, and nextAttemptAt for scheduled retries. For a manual resend, resendOfDeliveryId identifies the delivery selected by the user. This is the fastest way to debug a misbehaving webhook endpoint: if deliveries are retrying or failed, the response code and error tell you why.

To inspect history for one subscription, use GET /events?subscriptionId={subscriptionId} and open an event’s deliveries.

Inspect one delivery

Use the delivery id from the list to inspect the event payload and recent HTTP attempt history:

$curl "https://api.plextera.com/api/public/v1/events/evt_.../deliveries/dlv_..." \
> -H "Authorization: api-key YOUR_API_KEY"

The response returns up to the 100 most recent attempt records, ordered oldest first within that returned window. totalAttemptRecords is the total stored history count and attemptsTruncated: true means earlier records were omitted. attemptCount counts completed attempts whose result was applied to the delivery, so it can be lower than totalAttemptRecords when an in-progress or superseded attempt is retained.

Each attempt includes:

  • trigger - automatic or manual.
  • status and start/completion timestamps.
  • durationMs.
  • the actual endpoint URL used for that attempt.
  • safe request metadata. X-Plextera-Signature is shown as [redacted]; the secret and computed signature are not exposed in captured request headers. Treat the endpoint response body as opaque endpoint-controlled text.
  • the endpoint’s HTTP status and up to the first 16,000 characters of its response body.
  • a structured error with a category, code, and message when delivery failed.

The response object is omitted when Plextera received no HTTP response, for example after a DNS, TLS, connection, timeout, or endpoint-configuration failure. The error.category distinguishes those cases.

Delivery details can contain the original event payload and response data from your endpoint. The API returns them with Cache-Control: no-store; avoid copying them into logs that are accessible more broadly than the source data.

Resend an event manually

You can resend any delivery, including one that is already delivered, pending, or retrying:

$curl -X POST "https://api.plextera.com/api/public/v1/events/evt_.../deliveries/dlv_.../resend" \
> -H "Authorization: api-key YOUR_API_KEY"

The endpoint returns 202 Accepted with a new delivery in pending state. The original delivery is unchanged. A manual resend:

  • requires the original subscription to still exist and be active;
  • creates a new deliveryId and sets resendOfDeliveryId to the selected delivery;
  • keeps the same eventId and exact event payload;
  • uses the subscription’s current endpoint and signing secret;
  • makes one HTTP attempt and does not start an automatic retry sequence if that attempt fails;
  • creates another resend on every successful request, including concurrent requests.

A request can reach your endpoint even if Plextera does not receive its response. A resend can also intentionally deliver an event that your endpoint already processed. Keep the webhook handler idempotent and deduplicate using eventId.