API ReferenceCustomer lifecycle webhook

Customer-Level Lifecycle Webhook

The customer-level lifecycle webhook is one URL per account that receives every async event from every site you build — site.complete, site.failed, site.revision_complete, and friends. It’s the recommended path for production integrations and replaces polling GET /v1/sites/:id in a loop.

Five endpoints manage it:

MethodPathPurpose
GET/v1/customer/webhookRead the current configuration.
PUT/v1/customer/webhookSet (or change) the destination URL.
POST/v1/customer/webhook/regenerate-secretRotate the signing secret.
POST/v1/customer/webhook/test-pingFire a synthetic event at your receiver.
PATCH/v1/customer/webhook/subscriptionsChoose which event types to receive.

For the event catalog and per-type payload shapes, see Webhooks → Lifecycle Events. For signature verification, see Verifying Signatures.

Cost: Free. All five endpoints are unmetered.


GET /v1/customer/webhook

Read the current lifecycle-webhook configuration.

Request

curl https://api.warpweb.ai/v1/customer/webhook \
  -H "Authorization: Bearer $WARPWEB_KEY"

Response

{
  "url": "https://api.yourapp.com/webhooks/warpweb-lifecycle",
  "secret_preview": "whsec_a1…cd",
  "has_secret": true,
  "subscribed_events": [
    "site.research_ready",
    "site.complete",
    "site.failed",
    "site.revision_complete",
    "site.revision_failed",
    "site.revision_clarification_needed",
    "form.submit"
  ],
  "available_events": [
    "site.research_ready",
    "site.complete",
    "site.failed",
    "site.revision_complete",
    "site.revision_failed",
    "site.revision_clarification_needed",
    "form.submit"
  ],
  "auth_mode": "dual"
}
FieldDescription
urlThe configured destination URL, or null if not set.
secret_previewFirst and last 2 characters of the signing secret (e.g. whsec_a1…cd). The full secret is only ever returned once at creation/rotation — store it in your secret manager when you receive it.
has_secretBoolean. true once a secret has been generated.
subscribed_eventsThe event types you’ll receive. Defaults to the full list at first PUT; narrow with PATCH /v1/customer/webhook/subscriptions.
available_eventsAll event types Warpweb can deliver. Use this to check for new event types between releases.
auth_modeInternal — informational only.

PUT /v1/customer/webhook

Set or change the destination URL. The first call with a non-null URL auto-generates the signing secret and returns its plaintext once in secret_plaintext — capture it immediately. Subsequent calls only update the URL; rotate the secret via the dedicated endpoint (below).

Request

curl -X PUT https://api.warpweb.ai/v1/customer/webhook \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.yourapp.com/webhooks/warpweb-lifecycle"
  }'

Body

FieldTypeRequiredDescription
urlstring | nullyesHTTPS endpoint that will receive events. Pass null to disable delivery (subscriptions and secret are preserved).

Response — first configure (secret auto-generated)

{
  "url": "https://api.yourapp.com/webhooks/warpweb-lifecycle",
  "secret_preview": "whsec_a1…cd",
  "secret_generated_now": true,
  "secret_plaintext": "whsec_a1b2c3…64-char-hex…"
}

secret_plaintext is returned ONCE — on the first PUT with a non-null URL. Capture it into your secret manager before processing the response any further. If you lose it, rotate via POST /v1/customer/webhook/regenerate-secret.

Response — subsequent updates

{
  "url": "https://api.yourapp.com/webhooks/warpweb-lifecycle-v2",
  "secret_preview": "whsec_a1…cd",
  "secret_generated_now": false
}

secret_plaintext is omitted. The existing secret stays valid.

Errors

StatusBodyCause
400{ "error": "url must be http(s):// or null" }URL doesn’t match http(s):// shape.
404{ "error": "no_customer_for_caller" }Bearer token doesn’t resolve to a customer.
500{ "error": "persist_failed", "detail": "..." }Database write failed. Safe to retry.

POST /v1/customer/webhook/regenerate-secret

Explicitly rotate the signing secret. Use this immediately if you suspect a leak. The new secret takes effect on the next event delivery; the old secret is invalidated.

Request

curl -X POST https://api.warpweb.ai/v1/customer/webhook/regenerate-secret \
  -H "Authorization: Bearer $WARPWEB_KEY"

No body.

Response

{
  "secret": "whsec_a1b2c3…new-64-char-hex…",
  "secret_preview": "whsec_a1…cd",
  "rotated_at": "2026-05-20T14:30:00.000Z"
}

The new plaintext lives in secret and is returned once. Update your receiver’s secret store immediately. In-flight retries already in the queue are re-signed with the new secret on the next attempt.

For zero-downtime rotation, accept both the old and new secrets on your receiver for a few minutes while the change propagates.

Errors

StatusBodyCause
404{ "error": "no_customer_for_caller" }Bearer token doesn’t resolve to a customer.
500{ "error": "audit_insert_failed" } or { "error": "persist_failed" }Database write failed. Safe to retry.

POST /v1/customer/webhook/test-ping

Fire a synthetic event at your configured URL — useful for exercising your receiver before any real traffic. Synchronous, does not enqueue, 5-second timeout.

By default, sends a webhook.test_ping envelope (a benign “is your receiver up?” payload). Pass event_type in the body to simulate any real lifecycle event with its canonical sample payload — handy for testing your event-type router on more than just test_ping.

Request — default (test ping)

curl -X POST https://api.warpweb.ai/v1/customer/webhook/test-ping \
  -H "Authorization: Bearer $WARPWEB_KEY"

Request — sample a specific event type

curl -X POST https://api.warpweb.ai/v1/customer/webhook/test-ping \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "site.complete" }'

Body

FieldTypeRequiredDescription
event_typestringnoOne of the canonical event types from Lifecycle Events. Omit (or pass null) to send the default webhook.test_ping envelope.

Each ping uses a fresh event_id and occurred_at, so receivers can dedupe by event_id across multiple test pings. For customer-level pings, site_id is always null — a test ping isn’t about a specific site.

Response — success

{
  "ok": true,
  "status": 200,
  "response_body_preview": "ok",
  "duration_ms": 142,
  "event_type": "site.complete"
}
FieldDescription
oktrue if your receiver returned 2xx within the 5-second timeout.
statusHTTP status from your receiver (null on network failure or timeout).
response_body_previewYour receiver’s response body, truncated to 500 characters.
duration_msWall-clock time to first byte.
event_typeEcho of the event type that was fired.

Response — failure

{
  "ok": false,
  "status": null,
  "response_body_preview": null,
  "duration_ms": 5012,
  "error": "timeout after 5s",
  "event_type": "webhook.test_ping"
}

No retries — a transient failure surfaces here as ok: false and you decide whether to call again.

Errors

StatusBodyCause
400{ "ok": false, "error": "no_webhook_url_configured" }URL hasn’t been set yet — PUT /v1/customer/webhook first.
400{ "ok": false, "error": "no_signing_secret" }Secret missing (rare — implies a partially-configured row). Rotate via POST /v1/customer/webhook/regenerate-secret.
400{ "error": "unknown_event_type", "detail": "...", "available_events": [...] }event_type doesn’t match a known type.
404{ "error": "no_customer_for_caller" }Bearer token doesn’t resolve to a customer.

PATCH /v1/customer/webhook/subscriptions

Pick which event types you want to receive. Unsubscribed event types are still generated server-side; they just don’t get delivered to your URL.

By default (after first PUT), all subscribable event types are enabled.

Request

curl -X PATCH https://api.warpweb.ai/v1/customer/webhook/subscriptions \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["site.complete", "site.failed", "site.revision_complete", "site.revision_failed"]
  }'

Body

FieldTypeRequiredDescription
eventsstring[]yesThe event types you want to receive. Use the full list from GET /v1/customer/webhook’s available_events. Duplicates are de-duped. Empty array [] disables delivery (all events stop).

Note: webhook.test_ping is not subscribable — it’s a synthetic event that bypasses the subscription filter by design.

Response

{
  "subscribed_events": [
    "site.complete",
    "site.failed",
    "site.revision_complete",
    "site.revision_failed"
  ]
}

The full saved subscription list. The order matches what you sent; duplicates have been removed.

Errors

StatusBodyCause
400{ "error": "events must be an array of strings" }Body missing events, not an array, or array contained a non-string.
400{ "error": "unknown_event_type", "detail": "...", "available_events": [...] }One of the requested types isn’t a known event. Catches typos.
404{ "error": "no_customer_for_caller" }Bearer token doesn’t resolve to a customer.

End-to-end setup

The standard production setup:

# 1. Point the webhook at your receiver.
curl -X PUT https://api.warpweb.ai/v1/customer/webhook \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://api.yourapp.com/webhooks/warpweb-lifecycle" }'
# → capture the returned secret_plaintext into your secret manager
 
# 2. Optional: narrow the subscription to what you actually handle.
curl -X PATCH https://api.warpweb.ai/v1/customer/webhook/subscriptions \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "events": ["site.complete", "site.failed", "site.revision_complete", "site.revision_failed"] }'
 
# 3. Exercise the receiver before real traffic.
curl -X POST https://api.warpweb.ai/v1/customer/webhook/test-ping \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "event_type": "site.complete" }'
 
# 4. Build a site — your receiver gets site.complete within minutes.
curl -X POST https://api.warpweb.ai/v1/sites \
  -H "Authorization: Bearer $WARPWEB_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "businessName": "Acme Plumbing", "contactEmail": "owner@example.com" }'

For ongoing operations:

  • Use GET /v1/customer/webhook to confirm the current URL and subscription set.
  • Use POST /v1/customer/webhook/regenerate-secret immediately if you suspect a secret leak.
  • Use POST /v1/customer/webhook/test-ping when debugging a delivery problem to confirm your receiver is reachable and signing correctly.