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.

Shell
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"}'
  1. Save the signing_secret from the 201 response. It is shown only once. The response also has id, url, enabled and created_at. The secret is separate from your API key.
  2. Pass ?webhook_id=wh_… when submitting an async job. Only jobs that select this endpoint send events to it.
  3. 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

EventMeaning
watermark.embed.succeededVerified output is ready
watermark.embed.failedProcessing failed; credits held for the job are released
watermark.detect.succeededDetection finished; fetch the result to see whether a watermark was found
watermark.detect.failedDetection failed; credits held for the job are released
accelerator.readyA 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
JSON
{
  "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

HeaderValue
X-Etchv-Event-IDEvent ID; must match body id
X-Etchv-TimestampAttempt timestamp, in Unix seconds
X-Etchv-Signaturev1= plus an HMAC-SHA256 hex digest
X-Request-IDAssociated 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
  1. Compute the signature over timestamp + "." + raw_request_body using the entire signing secret, including whsec_.
  2. 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.
  3. Check that the body’s id matches the header. Save the event ID and queue your work in one step, so neither happens without the other.
  4. Return 2xx, also for duplicates you already saved. Download and process files outside the handler.
Python signature verification
Python
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.

BehaviorRule
AcknowledgmentAny 2xx
Automatic retryNetwork failure, timeout or any non-2xx, including redirects
Attempt limit10 total, over about 22 hours plus queue time
Handler timeout8-second socket timeout; 25-second gateway limit
Job resultAvailable 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

ActionEndpoint
ListGET /webhooks
CreatePOST /webhooks with { "url": "https://…" }
Enable / disablePATCH /webhooks/{id} with { "enabled": false }
DeleteDELETE /webhooks/{id}
Inspect deliveriesGET /webhooks/{id}/deliveries
RedeliverPOST /webhooks/{id}/deliveries/{event_id}/redeliver
List
Endpoint
GET /webhooks
Create
Endpoint
POST /webhooks with { "url": "https://…" }
Enable / disable
Endpoint
PATCH /webhooks/{id} with { "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.

View source on GitHub

Your privacy, your choice

We use essential cookies to keep Etchv working. Optional analytics helps us improve the site. Analytics is on by default; you can turn it off in preferences. PostHog loads only if you accept all. Privacy policy

ETCHV

Privacy preferences

Choose what you allow on this browser. Analytics is enabled by default. You can turn it off, and change your choice at any time.

Essential

Always active

Supports secure sign-in, account sessions, site security, and remembering your privacy choice. These are needed for Etchv to work.

Analytics

Helps us understand visits and improve the website using page-view and device statistics, which do not use cookies. PostHog also measures visits and campaigns and may set cookies; it loads only after you save a choice with analytics on. Turning this off stops future analytics events.

We do not load advertising scripts. Meeting calendars load only when you open them. These preferences do not change your account, watermarking requests, or asset storage.

Your choice is remembered for 180 days on this browser. Privacy policy