API reference
Webhooks
Etchv watermarking webhooks send signed events when a job finishes. Create an HTTPS endpoint, verify each delivery, and inspect or redeliver events.
A webhook is a message Etchv sends to your server when an async job finishes. Each message is signed so you can check it came from Etchv. Use webhooks to collect results without polling.
- Transport
- HTTPS · port 443
- Delivery
- Up to 10 attempts
- Endpoints
- 10 per organization
Create an endpoint
Use Dashboard webhooks or the API. To create or change endpoints, you must be an owner or admin and use a key with webhooks:write. To view them, use webhooks:read.
curl https://api.etchv.com/webhooks \
-H "X-API-Key: $ETCHV_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://your-app.com/webhooks/etchv"}'- Save the
signing_secretfrom the201response. It is shown only once. The response also hasid,url,enabledandcreated_at. The secret is separate from your API key. - Pass
?webhook_id=wh_…when submitting an async job. Only jobs that select this endpoint send events to it. - Verify each delivery, save its event ID and reply quickly.
Endpoint security and secret rotation
Use a public HTTPS URL. Do not put passwords or secret tokens in it. Etchv blocks private, loopback, link-local and reserved addresses on every delivery. Redirects are not followed.
To replace a lost secret or change it, create a new endpoint and use it in new requests. Disable the old endpoint once its pending deliveries finish.
Events
| Event | Meaning |
|---|---|
watermark. | Verified output is ready |
watermark. | Processing failed; credits held for the job are released |
watermark. | Detection finished; fetch the result to see whether a watermark was found |
watermark. | Detection failed; credits held for the job are released |
accelerator. | A requested GPU wake-up is ready |
watermark.embed. succeeded - Meaning
- Verified output is ready
watermark.embed. failed - Meaning
- Processing failed; credits held for the job are released
watermark.detect. succeeded - Meaning
- Detection finished; fetch the result to see whether a watermark was found
watermark.detect. failed - Meaning
- Detection failed; credits held for the job are released
accelerator.ready - Meaning
- A requested GPU wake-up is ready
Job events report only the final outcome. A single failed attempt that is retried sends no failure event. The event, the job’s completion and the credit charge are saved together.
Event payload example
{
"id": "evt_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"type": "watermark.embed.succeeded",
"api_version": "2026-09-12",
"created_at": "2026-09-12T14:30:00+00:00",
"data": {
"request_id": "req_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"status": "succeeded",
"operation": "embed",
"watermark_id": "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789",
"status_url": "/watermarks/jobs/req_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"result_url": "/watermarks/jobs/req_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef/result",
"error_code": null
}
}Job data includes the full receipt: format, units, credits, attempts, the webhook and asset IDs, and when the result expires. On failure, check error_code. Fetch files or detection results with your API key. Events never contain media, the original embedding JSON, API keys or download credentials.
For accelerator.ready, data contains the wake-up request’s id, accelerator, status, ready_at and warm_until.
Verify every delivery
| Header | Value |
|---|---|
X-Etchv-Event-ID | Event ID; must match body id |
X-Etchv-Timestamp | Attempt timestamp, in Unix seconds |
X-Etchv-Signature | v1= plus an HMAC-SHA256 hex digest |
X-Request-ID | Associated request ID |
X-Etchv-Event-ID- Value
- Event ID; must match body
id
X-Etchv-Timestamp- Value
- Attempt timestamp, in Unix seconds
X-Etchv-Signature- Value
v1=plus an HMAC-SHA256 hex digest
X-Request-ID- Value
- Associated request ID
- Compute the signature over
timestamp + "." + raw_request_bodyusing the entire signing secret, includingwhsec_. - Reject timestamps more than 300 seconds away from your clock. Compare signatures in constant time (a comparison that takes the same time whether or not they match). Do this before parsing JSON.
- Check that the body’s
idmatches the header. Save the event ID and queue your work in one step, so neither happens without the other. - Return
2xx, also for duplicates you already saved. Download and process files outside the handler.
Python signature verification
import hashlib
import hmac
import time
def verify_webhook(raw_body: bytes, headers, signing_secret: str) -> bool:
timestamp = headers.get("X-Etchv-Timestamp", "")
signature = headers.get("X-Etchv-Signature", "")
try:
if abs(time.time() - int(timestamp)) > 300:
return False
except (ValueError, TypeError):
return False
expected = "v1=" + hmac.new(
signing_secret.encode(),
timestamp.encode() + b"." + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)After this check, confirm the event ID matches and skip duplicates. Official SDKs also include signature helpers.
Delivery guarantees and retries
Expect duplicate events and events out of order. Each retry has the same event ID and JSON body, with a new timestamp and signature. The body shows the job at the time of the event, not its current status.
| Behavior | Rule |
|---|---|
| Acknowledgment | Any 2xx |
| Automatic retry | Network failure, timeout or any non-2xx, including redirects |
| Attempt limit | 10 total, over about 22 hours plus queue time |
| Handler timeout | 8-second socket timeout; 25-second gateway limit |
| Job result | Available even if delivery fails; a failed delivery adds no watermarking charge |
- Acknowledgment
- Rule
- Any
2xx
- Automatic retry
- Rule
- Network failure, timeout or any non-
2xx, including redirects
- Attempt limit
- Rule
- 10 total, over about 22 hours plus queue time
- Handler timeout
- Rule
- 8-second socket timeout; 25-second gateway limit
- Job result
- Rule
- Available even if delivery fails; a failed delivery adds no watermarking charge
Retry schedule
Delays between attempts: 30 seconds, 2 minutes, 10 minutes, 30 minutes, 1 hour, 2 hours, 4 hours, 6 hours and 8 hours.
Inspect and redeliver
| Action | Endpoint |
|---|---|
| List | GET |
| Create | POST with { "url": "https: |
| Enable / disable | PATCH with { "enabled": false } |
| Delete | DELETE |
| Inspect deliveries | GET |
| Redeliver | POST |
- List
- Endpoint
GET/webhooks
- Create
- Endpoint
POSTwith/webhooks { "url": "https:/ /…" }
- Enable / disable
- Endpoint
PATCHwith/webhooks /{id} { "enabled": false }
- Delete
- Endpoint
DELETE/webhooks /{id}
- Inspect deliveries
- Endpoint
GET/webhooks /{id} /deliveries
- Redeliver
- Endpoint
POST/webhooks /{id} /deliveries /{event_id} /redeliver
The dashboard shows each delivery’s status, payload and attempts. The API list returns data and next_cursor. Pass next_cursor as after to get the next page. Records last 30 days, and the last 30 attempts are kept. Your server’s response bodies are not saved.
Redelivery, disabling and deletion
You can manually redeliver a delivered, exhausted or cancelled event up to 10 times while its record is kept. The endpoint must be enabled. An event that is still pending cannot be redelivered at the same time.
Disabling or deleting an endpoint cancels its deliveries at their next send. Requests already on the way may still arrive. Turning the endpoint back on does not resend cancelled events; redeliver them yourself. Deletion is permanent.
Customer storage delivery events
When an async embed sets both webhook_id and storage_destination_id, Etchv also sends storage.delivery.succeeded or storage.delivery.failed after the upload to your bucket finishes.
Storage data contains delivery_id, asset_id, request_id, destination_id, status, error_code, uri and public_url (null for private storage). Verify the signature and skip duplicates as usual, and do not assume events arrive in order. A manual upload retry creates a new final event. See storage.