Skip to content

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.

  1. Select your environment and open its webhook settings.
  2. Add your backend’s receiver URL. Use a publicly reachable HTTPS endpoint and enter its final URL, without redirects.
  3. Choose the templates it should cover, or make it the global endpoint for templates without a dedicated webhook.
  4. Choose the events you want to receive and save the endpoint as active.
  5. Store the signing secret securely on your backend.
  6. Submit an async job with deliveryMode=webhook.
SettingHow to use it
ScopeA scoped endpoint covers selected templates. A global endpoint covers templates without an active scoped endpoint. You can have one active global endpoint per environment.
EventsSubscribe to extraction.job.completed, extraction.job.failed, or both.
Signing secretVerify incoming signatures with this secret. Update your receiver when you rotate it.
Outbound authenticationOptionally require Authorization: Bearer <token> or X-API-Key: <token> on deliveries.
Maximum in-flight deliveriesChoose a limit from 1 to 10 simultaneous deliveries to match your receiver’s capacity. The default is 10.

Create a job with the file, a saved template or filter name, and deliveryMode=webhook:

Terminal window
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.

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.

{
"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.

{
"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.

HeaderValue
Content-Typeapplication/json
X-Optica-EventMatches the body’s type.
X-Optica-DeliveryMatches the body’s id. Retries use the same delivery ID.
X-Optica-TimestampUnix timestamp in seconds.
X-Optica-Signaturev1= followed by a lowercase hexadecimal HMAC-SHA256 digest.

Deliveries also include your configured outbound authentication header, if any.

Verify the signature using the endpoint’s signing secret and the raw request body. The signed content is:

${timestamp}.${rawBody}
  1. Capture the raw body bytes before parsing JSON.
  2. Read X-Optica-Timestamp. Require a valid timestamp within five minutes of your receiver’s current time, and keep its clock synchronized.
  3. Read X-Optica-Signature and require the v1= prefix followed by a valid SHA-256 hexadecimal digest.
  4. Compute HMAC_SHA256(secret, timestamp + "." + rawBody).
  5. Decode both digests to bytes, check their lengths, and compare them with a constant-time comparison function.
  6. 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.

  1. Verify the signature and any outbound authentication header you configured.
  2. Check the delivery ID. If you already accepted it, return 2xx without repeating its effects.
  3. Save or enqueue the event durably, then return 2xx promptly.
  4. Process the saved event in the background. For a completed job, fetch job.resultUrl with your environment’s API key. For a failed job, read job.statusUrl for details.

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.

If your receiver is temporarily unavailable, return 429 or 503 with Retry-After, for example:

Retry-After: 60

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

These errors are returned when creating a job. Update the request or webhook settings before submitting it again.

CodeWhat to check
INVALID_DELIVERY_MODESet deliveryMode=webhook for webhook delivery.
FILTER_HAS_NO_EXTRACTABLE_TEMPLATESGive the filter at least one template to extract.
WEBHOOK_NOT_CONFIGUREDConfigure an active endpoint in the environment.
WEBHOOK_NOT_CONFIGURED_FOR_TEMPLATECover the template with an active scoped or global endpoint.
WEBHOOK_TEMPLATE_ROUTING_CONFLICTCheck the template scopes so the request resolves to the intended endpoint.

Each returns HTTP 400. For authentication and other request errors, see Authentication & Errors.