Webhooks
Receive automatic delivery notifications when a correction job completes
Instead of polling GET /v2/status/{taskId}, you can supply a webhookUrl on your /v2/pfc request and Perfectly Clear will POST the result to your endpoint the moment the job reaches COMPLETED or FAILED.
Webhooks also fire on cache hits — if the same image with the same parameters was processed before, the webhook still delivers the cached result.
Adding a webhook URL
Append webhookUrl as a query parameter on your /v2/pfc call. The value must be URL-encoded and must use HTTPS — a plain http:// URL returns a 400 error before any processing begins.
# URL-encode your endpoint first, e.g. https://example.com/hook → https%3A%2F%2Fexample.com%2Fhook
curl -s -X GET "https://api.perfectlyclear.io/v2/pfc?fileKey=${fileKey}&webhookUrl=https%3A%2F%2Fexample.com%2Fhook" \
-H "X-API-KEY: ${PFC_API_KEY}"If webhookUrl is omitted the API behaves exactly as before — the job completes normally and you can retrieve the result by polling GET /v2/status/{taskId}.
Webhook request
When the job finishes, Perfectly Clear sends a POST request to your endpoint with the following headers and body.
Headers
| Header | Format | Description |
|---|---|---|
webhook-id | evt_{taskId} | Unique event identifier |
webhook-timestamp | Unix seconds | Time the webhook was sent |
webhook-signature | v1,<base64> | HMAC-SHA256 signature (see Verifying signatures) |
Content-Type | application/json | Always JSON |
User-Agent | EyeQ-Webhook/1.0 | Identifies the sender |
Body
The body is identical to the response of GET /v2/status/{taskId} and includes fields such as _id, status, correctedFile, usage, originalDimensions, and others.
{
"_id": "abc123",
"status": "COMPLETED",
"correctedFile": "https://...",
"usage": { ... },
"originalDimensions": { "width": 3000, "height": 2000 }
}Verifying signatures
The webhook-signature header proves the request came from Perfectly Clear and not a third party.
Algorithm: HMAC-SHA256
Secret: the API key used in the /v2/pfc request
Signed content: {webhook-id}.{webhook-timestamp}.{raw_body} — literal dots, raw JSON body (do not re-serialize)
The v1, prefix is a versioning marker. If the signing algorithm ever changes (for example, to Ed25519) the prefix becomes v1a, allowing receivers to support both during migration.
Quick verification with shell
ID="evt_abc123"
TS="1714500000"
BODY='{"_id":"abc123","status":"COMPLETED",...}'
APIKEY="${PFC_API_KEY}"
EXPECTED=$(printf "%s.%s.%s" "$ID" "$TS" "$BODY" \
| openssl dgst -sha256 -hmac "$APIKEY" -binary \
| base64)
echo "v1,$EXPECTED"
# Compare this output to the webhook-signature header — they must match exactly.What to test
- Signature matches when computed with the correct API key.
- Signature changes when
webhook-timestampchanges (replay protection — include the timestamp in signed content so receivers can reject deliveries older than a chosen tolerance window). - Signature does not match if you re-format the body (e.g.
JSON.parsethenJSON.stringifymay reorder keys or change whitespace). - Signature does not match if you use a different API key.
Retries
If your endpoint returns a non-2xx status code, Perfectly Clear retries delivery 8 times over 48 minutes. After all retries are exhausted the job itself is unaffected — GET /v2/status/{taskId} still returns the result.
Testing with webhook.site
webhook.site provides a free, disposable HTTPS receiver — useful for verifying end-to-end delivery before wiring up your own endpoint.
- Open webhook.site — a unique URL is generated automatically (e.g.
https://webhook.site/08f51eb0-...). Keep the tab open to inspect incoming requests in real time. - URL-encode that URL (e.g. using urlencoder.org).
- Submit a correction request with the encoded URL as
webhookUrl. - When the job finishes, the browser tab refreshes automatically. Verify the headers and body match what you expect.
To test retry behavior, set the Default response code in webhook.site's top-right dashboard to 500 and re-run the request — you should see 8 retry attempts arrive over the next 48 minutes.
WEB-API Version 2 built on 05-14-2026.
Copyright © 2026 EyeQ Imaging Inc. All rights reserved.