Webhooks
Warpweb sends two kinds of outbound webhooks:
- Lifecycle Events — fired when an async build or revision changes state. Delivered to the customer-level URL you configure with
PUT /v1/customer/webhook. Recommended over polling. - Form Submissions — fired when someone fills out a form on a deployed site. Delivered to the per-site URL you configure with
POST /v1/sites/:id/webhooks/forms.
All webhooks are signed with HMAC-SHA256. Always verify signatures — see Verifying Signatures for copy-paste snippets in multiple languages.
Envelope shape
Every webhook POST carries this envelope:
{
"event_id": "00000000-0000-0000-0000-000000000001",
"type": "site.complete",
"site_id": "11111111-1111-1111-1111-111111111111",
"occurred_at": "2026-05-18T12:05:00.000Z",
"payload": {
"...": "type-specific fields"
}
}The per-site form-submission webhook uses the same envelope with type: "form.submit". See the per-type pages for payload shapes.
Endpoint requirements
Your webhook receiver should:
- Verify
X-Warpweb-Signatureagainst the raw request body using your stored secret. See Verifying Signatures. - Parse JSON and inspect
type(envelope field) orX-Warpweb-Event-Typeheader. - Dedupe on
event_idif you’ve seen it before — return 200 immediately for duplicates. - Process the event, or enqueue it for async processing.
- Return 2xx within 10 seconds.
If your processing is slow, enqueue and return 200 immediately. Don’t make Warpweb wait — it’s better to acknowledge fast and retry on your own queue than to time out and trigger our retry loop. (Any queue works — AWS SQS, GCP Pub/Sub, BullMQ, Sidekiq, or an INSERT into your own DB and a background worker.)
Common conventions
- All deliveries are
POSTwithContent-Type: application/json. - All payloads include an
event_id(UUID). Use it as your idempotency key — duplicates can happen. - Signature lives in the
X-Warpweb-Signatureheader (sha256=<hex>). - Timestamp lives in the
X-Warpweb-Timestampheader (Unix seconds) — used for replay protection. Receivers should reject requests whose timestamp is more than 300 seconds (5 minutes) from local wall clock. - Event type echoed in
X-Warpweb-Event-Typeso a router can dispatch without parsing the body. - User-Agent is
Warpweb-Webhook/2.0. - Receiver timeout: 10 seconds. Return a 2xx response within that window.
- Retry schedule on 5xx / network errors: immediate, +30s, +5min, +30min. After that, dead-letter.
- 4xx responses are not retried — they’re treated as permanent rejections.
Lifecycle event types
The customer-level webhook can deliver these event types. Subscribe to the ones you care about via PATCH /v1/customer/webhook/subscriptions:
type | Fires when |
|---|---|
site.research_ready | Initial research complete (mid-build status). |
site.complete | An initial POST /v1/sites build deployed successfully. |
site.failed | An initial build failed. No credits were charged. |
site.revision_complete | A POST /v1/sites/:id/revisions revision deployed successfully. |
site.revision_failed | A revision failed (or was ruled out of scope). No credits charged. |
site.revision_clarification_needed | The agent needs more info before proceeding. Reply with another revision call. |
form.submit | Same payload as the per-site form-submission webhook. Useful if you’d rather receive everything on one URL. |
In addition, the test-ping endpoints (POST /v1/customer/webhook/test-ping and POST /v1/sites/:id/webhook/test-ping) send one synthetic event with type: "webhook.test_ping". It’s not subscribable — receivers should accept it and no-op. If your router rejects unknown event types, special-case webhook.test_ping so synthetic pings pass through.
See Lifecycle Events for full per-type payload shapes.