When you provide aDocumentation Index
Fetch the complete documentation index at: https://docs.cloro.dev/llms.txt
Use this file to discover all available pages before exploring further.
webhook.url on an async task, cloro delivers the result via an HTTP POST to your endpoint when the task completes. Anyone who can reach your endpoint could send a forged request that looks identical to a real cloro delivery unless you verify the signature.
Webhook signing is opt-in per organization. Once enabled, every outbound delivery from cloro includes three headers your endpoint can use to confirm the payload’s origin and integrity.
Enabling signing
Webhook signing is enabled from the dashboard. When you enable it, cloro generates a secret prefixed withwhsec_ and shows it to you exactly once. Copy it immediately and store it in your secret manager — it’s the only thing your endpoint needs in order to verify signatures.
What we send
Every signed delivery includes three headers in addition to the standardContent-Type: application/json:
| Header | Example | Purpose |
|---|---|---|
X-Cloro-Timestamp | 1748419200 | Unix timestamp (seconds) when cloro signed the delivery |
X-Cloro-Signature | v1=ab12cd34... | HMAC-SHA256 signature, prefixed with the scheme version |
X-Cloro-Webhook-Id | task_abc123-1 | Unique delivery ID (<task-id>-<attempt>) for deduplication |
How the signature is computed
v1= portion of X-Cloro-Signature. Your endpoint recomputes the same value and compares it to the header.
The timestamp goes inside the signed payload so an attacker can’t replay an intercepted webhook against you indefinitely — your endpoint can reject anything signed more than a few minutes ago.
Verifying a webhook
Step 1 — capture the raw body
The signature is computed over the exact bytes of the request body. If your web framework parses the JSON and then re-serializes it before you see it (Express’sexpress.json() does this, as does Flask’s request.json when the body type is detected as JSON), the bytes you check can differ from the bytes cloro signed — different float formatting, key ordering, or whitespace — and verification will silently fail.
Always capture the raw bytes first, then parse the JSON only after verification has passed.
Step 2 — verify the signature
The 5-minute tolerance is what we recommend — adjust if your endpoint sits
behind slow networks or you want stricter replay protection.
Common pitfalls
- Parsing the body before verifying — the signature is over the raw bytes. Frameworks that parse-then-restringify (Express’s
express.json(), some serverless wrappers) can change byte representation and break verification. Always read the raw bytes first. - String equality instead of constant-time compare — a
==comparison on the hex strings leaks information about the expected signature via timing differences. Usecrypto.timingSafeEqual(Node),hmac.compare_digest(Python), orhmac.Equal(Go). - No timestamp check — without rejecting old timestamps, an attacker who intercepts even one signed webhook can replay it against you indefinitely. The timestamp is in the signed payload specifically to make this check possible.
- Storing the secret in source code — if a secret leaks, rotate it immediately from the dashboard. The secret is the only thing standing between an attacker and a forged event.
Retries and deduplication
cloro retries failed deliveries up to 5 attempts with exponential backoff. TheX-Cloro-Webhook-Id header is unique per delivery attempt (<task-id>-<attempt>), so the same logical event may arrive at your endpoint multiple times with different IDs.
If you need exactly-once handling, deduplicate on the task ID inside the payload (event.task.id), not on the webhook ID.
Disabling or rotating
You can rotate or disable signing at any time from the dashboard.- Rotating generates a new secret immediately. Your existing receivers will reject signatures until you update them with the new value, so coordinate the rotation with your deploy.
- Disabling stops sending the
X-Cloro-*headers. Existing receivers that verify will start rejecting payloads until you remove the verification check on their side.
Need help?
- Reach out at support@cloro.dev
- Async task reference:
POST /v1/async/task - Authentication: API keys & Bearer tokens