Summary
We recently stood up an inbound webhook route against a Stripe-style HMAC-timestamped provider and hit several things that are answerable from the source but not currently covered in a user-facing guide. Proposing a single "Operating & rotating webhook routes" guide so the next person doesn't have to read WebhookEndpointRouteBuilderExtensions / WebhookRequestVerifier to figure them out.
All content below is framework-general — no secrets or private data required to write it. Provider names (Stripe, and providers that sign Stripe-style like some email gateways) appear only as generic examples.
Proposed guide sections
1. Rotating a signing secret without recreating the route
netclaw webhooks set <route> --update-only --secret-file <path> updates just the secret; everything else on the route is preserved.
- Route files hot-reload — no daemon restart needed.
- Prefer
--secret-file / --secret-env over --secret <value> so the secret never lands in shell history.
- Note that the route JSON stores the secret; rotating means updating it in both the provider and the route.
2. Reading delivery outcomes (and what each status means)
Document the request pipeline order, because it makes debugging deterministic:
route lookup → body read → signature verification → event allowlist → dedup / rate-limit → dispatch
401 Unauthorized → signature verification failed (wrong key, wrong signed-payload construction, or stale timestamp). Verification runs before the event allowlist, so a bad signature never reaches filtering.
202 with WebhookIgnoredResponse (reason: event_filtered) → the signature passed; the event type just isn't in the route's --events allowlist.
202 with WebhookAcceptedResponse → verified, allowed, dispatched (a session is created).
The practical tip worth calling out: because verification precedes filtering, a 202 event_filtered response is a perfect setup smoke-test — it confirms your secret/headers are correct before a real (allowlisted) event ever arrives. A provider's "send test" button (which usually sends a non-allowlisted event type) is enough to prove the signature path.
3. Configuring an HMAC-timestamped (Stripe-style) provider
--verification-kind hmac-timestamped, plus --signature-header, --event-header, --delivery-header.
- Wiring
--event-header is what makes the --events allowlist and delivery-ID dedup actually work.
- Defaults that match Stripe-style providers and usually shouldn't be overridden:
timestamp-field t, signature-field v1, signed-payload-separator ., signature-tolerance-seconds 300.
- The signature is HMAC-SHA256 with key
UTF8(secret) over "{t}.{rawBody}", emitted as lowercase hex — computed over the raw received body.
4. Debugging a persistent 401
- Confirm what the provider actually signs with: the guide should stress verifying that the provider signs the raw body with the plaintext secret you were handed (not a hashed/derived/transformed key). A provider bug or a copy-paste of the wrong secret form is a common cause.
- Where deliveries log, and how to distinguish a
verification_failed from an event_filtered.
- How to reveal/inspect the registered secret (
netclaw webhooks show <route> --show-secret).
5. Webhook (and reminder) sessions run headless — pre-approve tools
- Webhook- and reminder-triggered sessions are non-interactive, so any tool the handler prompt calls must be pre-approved (e.g. in
tool-approvals.json) for the route's audience, or the call fails closed with no human to approve it.
- Worth a cross-link from the reminders/scripting docs too, since it's the same failure mode.
Why
Each of these took a source read or trial-and-error to discover; none are hard once written down. A short guide (plus a couple of netclaw webhooks set --help cross-references) would cover the full lifecycle: register → verify → operate → rotate → debug.
Summary
We recently stood up an inbound webhook route against a Stripe-style HMAC-timestamped provider and hit several things that are answerable from the source but not currently covered in a user-facing guide. Proposing a single "Operating & rotating webhook routes" guide so the next person doesn't have to read
WebhookEndpointRouteBuilderExtensions/WebhookRequestVerifierto figure them out.All content below is framework-general — no secrets or private data required to write it. Provider names (Stripe, and providers that sign Stripe-style like some email gateways) appear only as generic examples.
Proposed guide sections
1. Rotating a signing secret without recreating the route
netclaw webhooks set <route> --update-only --secret-file <path>updates just the secret; everything else on the route is preserved.--secret-file/--secret-envover--secret <value>so the secret never lands in shell history.2. Reading delivery outcomes (and what each status means)
Document the request pipeline order, because it makes debugging deterministic:
401 Unauthorized→ signature verification failed (wrong key, wrong signed-payload construction, or stale timestamp). Verification runs before the event allowlist, so a bad signature never reaches filtering.202withWebhookIgnoredResponse(reason: event_filtered) → the signature passed; the event type just isn't in the route's--eventsallowlist.202withWebhookAcceptedResponse→ verified, allowed, dispatched (a session is created).The practical tip worth calling out: because verification precedes filtering, a
202 event_filteredresponse is a perfect setup smoke-test — it confirms your secret/headers are correct before a real (allowlisted) event ever arrives. A provider's "send test" button (which usually sends a non-allowlisted event type) is enough to prove the signature path.3. Configuring an HMAC-timestamped (Stripe-style) provider
--verification-kind hmac-timestamped, plus--signature-header,--event-header,--delivery-header.--event-headeris what makes the--eventsallowlist and delivery-ID dedup actually work.timestamp-field t,signature-field v1,signed-payload-separator .,signature-tolerance-seconds 300.UTF8(secret)over"{t}.{rawBody}", emitted as lowercase hex — computed over the raw received body.4. Debugging a persistent 401
verification_failedfrom anevent_filtered.netclaw webhooks show <route> --show-secret).5. Webhook (and reminder) sessions run headless — pre-approve tools
tool-approvals.json) for the route's audience, or the call fails closed with no human to approve it.Why
Each of these took a source read or trial-and-error to discover; none are hard once written down. A short guide (plus a couple of
netclaw webhooks set --helpcross-references) would cover the full lifecycle: register → verify → operate → rotate → debug.