Webhooks
Receive a signed notification when an async extraction job completes or fails. Each event identifies the job and gives you a URL to fetch its result or check the error. Use this to connect extraction to your backend’s processing flow.
Set Up Webhooks In The App
Section titled “Set Up Webhooks In The App”- Select your environment and open its webhook settings.
- Add your backend’s receiver URL. Use a publicly reachable HTTPS endpoint and enter its final URL, without redirects.
- Choose the templates it should cover, or make it the global endpoint for templates without a dedicated webhook.
- Choose the events you want to receive and save the endpoint as active.
- Store the signing secret securely on your backend.
- Submit an async job with
deliveryMode=webhook.
Endpoint Settings
Section titled “Endpoint Settings”| Setting | How to use it |
|---|---|
| Scope | A scoped endpoint covers selected templates. A global endpoint covers templates without an active scoped endpoint. You can have one active global endpoint per environment. |
| Events | Subscribe to extraction.job.completed, extraction.job.failed, or both. |
| Signing secret | Verify incoming signatures with this secret. Update your receiver when you rotate it. |
| Outbound authentication | Optionally require Authorization: Bearer <token> or X-API-Key: <token> on deliveries. |
| Maximum in-flight deliveries | Choose a limit from 1 to 10 simultaneous deliveries to match your receiver’s capacity. The default is 10. |
Use Webhook Delivery
Section titled “Use Webhook Delivery”Create a job with the file, a saved template or filter name, and
deliveryMode=webhook:
curl -X POST "https://api.terrafai.com/api/v1/extraction-jobs" \ -H "Authorization: api-key $API_KEY" \ -H "Idempotency-Key: invoice-1001-upload-1" \ -H "X-Client-Reference: account-42:invoice-1001" \ -F "file=@invoice.pdf;type=application/pdf" \ -F "templateName=invoice" \ -F "deliveryMode=webhook"Save the returned job ID. Use your environment’s API key to fetch the full result after a completion event. See Asynchronous Extraction for the response fields and result retention.
If you enable document review, the completion event follows approval of the result.
Route Events By Template
Section titled “Route Events By Template”With templateName, delivery goes to the active scoped endpoint for that
template, or to the global endpoint if no scoped endpoint covers it.
With filterName, configure webhook coverage for every extractable template
in the filter. One job can produce a completed event for each endpoint whose
templates matched. The event’s document summaries identify the documents for
that endpoint; the result URL returns the full job result.
For example, an invoice template can send notifications to your accounts service while a bank-statement template sends them to a separate receiver. Set this up through template scopes in the app, then select the filter in your upload request.
Webhook Payloads
Section titled “Webhook Payloads”Completed Event
Section titled “Completed Event”{ "id": "delivery_id", "type": "extraction.job.completed", "createdAt": "2026-03-16T09:23:09.152Z", "clientReference": "account-42:invoice-1001", "routing": { "webhookId": "webhook_id", "scopeType": "scoped", "templates": [ { "id": "template_invoice", "name": "invoice" } ] }, "job": { "id": "job_id", "status": "completed", "resultUrl": "https://api.terrafai.com/api/v1/extraction-jobs/job_id/result" }}For filter jobs, a completed event can also include document summaries:
{ "documents": [ { "documentName": "Invoice", "templateId": "template_invoice", "templateName": "invoice", "sourcePages": [1] } ]}Document summaries can also include sharedPages when a source page belongs
to more than one document or section. Use it with sourcePages to connect each
summary to the original upload. See
Shared Pages When Splitting.
Failed Event
Section titled “Failed Event”{ "id": "delivery_id", "type": "extraction.job.failed", "createdAt": "2026-03-16T09:23:09.152Z", "clientReference": "account-42:invoice-1001", "routing": { "webhookId": "webhook_id", "scopeType": "scoped", "templates": [ { "id": "template_invoice", "name": "invoice" } ] }, "job": { "id": "job_id", "status": "failed", "statusUrl": "https://api.terrafai.com/api/v1/extraction-jobs/job_id" }}clientReference is included when it was supplied on job creation. Use
job.id to associate the event with the saved job and id to identify the
delivery.
Webhook Headers
Section titled “Webhook Headers”| Header | Value |
|---|---|
Content-Type | application/json |
X-Optica-Event | Matches the body’s type. |
X-Optica-Delivery | Matches the body’s id. Retries use the same delivery ID. |
X-Optica-Timestamp | Unix timestamp in seconds. |
X-Optica-Signature | v1= followed by a lowercase hexadecimal HMAC-SHA256 digest. |
Deliveries also include your configured outbound authentication header, if any.
Signature Verification
Section titled “Signature Verification”Verify the signature using the endpoint’s signing secret and the raw request body. The signed content is:
${timestamp}.${rawBody}- Capture the raw body bytes before parsing JSON.
- Read
X-Optica-Timestamp. Require a valid timestamp within five minutes of your receiver’s current time, and keep its clock synchronized. - Read
X-Optica-Signatureand require thev1=prefix followed by a valid SHA-256 hexadecimal digest. - Compute
HMAC_SHA256(secret, timestamp + "." + rawBody). - Decode both digests to bytes, check their lengths, and compare them with a constant-time comparison function.
- Process the event only after verification succeeds.
Use the exact body bytes received. Parsing and re-serializing JSON can change them and invalidate the signature. Keep signing secrets on your backend.
Handle A Delivery
Section titled “Handle A Delivery”- Verify the signature and any outbound authentication header you configured.
- Check the delivery ID. If you already accepted it, return
2xxwithout repeating its effects. - Save or enqueue the event durably, then return
2xxpromptly. - Process the saved event in the background. For a completed job, fetch
job.resultUrlwith your environment’s API key. For a failed job, readjob.statusUrlfor details.
Attempts And Timeouts
Section titled “Attempts And Timeouts”The default policy allows three attempts in total. Retries normally wait two
seconds, then four seconds; receiver capacity and cooldowns can extend these
waits. A delivery succeeds on HTTP 2xx.
Network failures, timeouts, and HTTP 408, 409, 425, 429, or 5xx can
be retried. Other non-2xx responses stop that delivery. Configure the final
receiver URL directly because redirects are not followed.
The default request timeout is five seconds. Acknowledge after saving the event, before doing slower work such as fetching results. Your receiver may accept an event just before a timeout, so handle repeated delivery IDs safely.
If a notification does not arrive, use the saved job ID to check status and retrieve the result before it expires. An unsuccessful delivery does not require the document to be extracted again.
Ask For A Retry Delay
Section titled “Ask For A Retry Delay”If your receiver is temporarily unavailable, return 429 or 503 with
Retry-After, for example:
Retry-After: 60The header accepts seconds or a future HTTP date. Deliveries to that endpoint pause for the requested delay, capped at fifteen minutes. Other endpoints continue independently. The failed HTTP attempt still counts toward the three-attempt budget.
Webhook Errors
Section titled “Webhook Errors”These errors are returned when creating a job. Update the request or webhook settings before submitting it again.
| Code | What to check |
|---|---|
INVALID_DELIVERY_MODE | Set deliveryMode=webhook for webhook delivery. |
FILTER_HAS_NO_EXTRACTABLE_TEMPLATES | Give the filter at least one template to extract. |
WEBHOOK_NOT_CONFIGURED | Configure an active endpoint in the environment. |
WEBHOOK_NOT_CONFIGURED_FOR_TEMPLATE | Cover the template with an active scoped or global endpoint. |
WEBHOOK_TEMPLATE_ROUTING_CONFLICT | Check the template scopes so the request resolves to the intended endpoint. |
Each returns HTTP 400. For authentication and other request errors, see
Authentication & Errors.