Skip to content

feat: webhook docs#24

Merged
Tom Tang (qwerzl) merged 1 commit into
mainfrom
ENG-1316-Spectrum-Webhook-Docs
May 14, 2026
Merged

feat: webhook docs#24
Tom Tang (qwerzl) merged 1 commit into
mainfrom
ENG-1316-Spectrum-Webhook-Docs

Conversation

@yanxue06
Copy link
Copy Markdown
Member

@yanxue06 Yan Xue (yanxue06) commented May 14, 2026


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by CodeRabbit

  • Documentation
    • Added comprehensive webhooks documentation suite: quickstart guides for setup and testing, event format and payload specifications, HMAC signature verification with code examples across multiple frameworks (Bun/Hono, Node/Express, Python/FastAPI, Go), webhook registration and management via API, delivery behavior with retry guarantees, security practices for secret handling and rotation, and detailed troubleshooting guidance for common integration issues.

Review Change Stack

Copilot AI review requested due to automatic review settings May 14, 2026 20:54
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

📝 Walkthrough

Walkthrough

This PR adds comprehensive webhook documentation for the Spectrum platform, covering registration, event delivery, security, management API, and troubleshooting. Navigation is updated with a new Webhooks tab, and seven new MDX pages document the full webhook lifecycle from setup through operations.

Changes

Spectrum Webhooks Documentation

Layer / File(s) Summary
Navigation and core mental model
docs.json, webhooks/overview.mdx
Navigation adds a Webhooks tab with Getting Started and Implementation groups. Overview establishes the mental model (per-project per-URL ownership, per-URL signing secrets, at-least-once delivery), explains the end-to-end flow, decision criteria for webhooks vs SDK loop, and currently emitted event types (messages).
Implementation walkthrough
webhooks/quickstart.mdx
Step-by-step guide to set up a local webhook endpoint using Bun + Hono, register the webhook URL via curl, capture the signing secret, and implement signature verification with HMAC and timestamp validation before processing events.
Webhook event wire format and payload structure
webhooks/events.mdx
Specification of the HTTP POST anatomy including four required custom headers, example messages event request, payload structure with space/message objects and discriminated content union, and forward-compatibility rules for unknown event types and fields.
Signature verification deep dive
webhooks/verifying-signatures.mdx
Complete verification recipe (raw-body hashing, constant-time comparison, timestamp staleness check, v0= prefix casing) with working examples across Bun+Hono, Node+Express, Python+FastAPI, and Go, plus a common-failures table and reusable verifier code snippet.
Webhook management API and lifecycle
webhooks/managing-webhooks.mdx
HTTP Basic auth endpoints for registering webhooks (POST with one-time signingSecret), listing registered webhooks (GET, excluding secrets), deletion (soft-delete), and signing-secret rotation via delete-and-recreate with a dual-secret verification pattern during overlap windows.
Delivery guarantees and retry policy
webhooks/delivery.mdx
Delivery contract specifying bounded retry window, per-attempt timeout, status-class-based retry/give-up behavior, at-least-once semantics with no durable queue, handler guidance (ack fast after verification and queuing, implement idempotency), burst handling, failure scenarios, and when to combine webhooks with SDK loop reconciliation.
Symptom-based troubleshooting guide
webhooks/troubleshooting.mdx
Triage guide covering signature verification failures with framework-specific raw-body capture, clock skew and secret mismatches, missing deliveries, duplicate handling, request timeouts with ack-and-queue pattern, ngrok URL changes, secret-leak rotation, offline verification testing, and escalation checklist.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Webhooks take flight, signed and bright,
Events flow via HTTPS in the night,
Verify with HMAC, ack fast, don't delay,
Spectrum webhooks are here to stay! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'feat: webhook docs' is vague and generic. While it indicates webhook documentation is being added, it lacks specificity about what aspect of webhooks or what the main deliverable is. Consider a more specific title that captures the main content, such as 'feat: add comprehensive webhook documentation with setup, delivery, and verification guides' or 'feat: add webhook API documentation with quickstart and troubleshooting'.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ENG-1316-Spectrum-Webhook-Docs

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new "Webhooks" documentation section covering the registration, signing, delivery, and troubleshooting story for Spectrum's HTTP webhook product, and wires the new pages into the docs navigation.

Changes:

  • Adds seven new MDX pages under webhooks/ (overview, quickstart, events, verifying-signatures, delivery, managing-webhooks, troubleshooting).
  • Registers a new "Webhooks" tab in docs.json with two groups (Getting Started, Implementation).
  • Documents the signing scheme (v0= HMAC-SHA256), retry policy, and at-least-once delivery semantics.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs.json Adds the Webhooks tab and groups to the docs navigation.
webhooks/overview.mdx High-level intro, mental model, and security TL;DR.
webhooks/quickstart.mdx End-to-end Bun + Hono + ngrok walkthrough.
webhooks/events.mdx Wire-format spec for headers and JSON body.
webhooks/verifying-signatures.mdx Verifier examples in Bun, Node, Python, Go and pitfalls.
webhooks/delivery.mdx Retry policy, status-code handling, and at-least-once contract.
webhooks/managing-webhooks.mdx API workflows for register/list/delete and secret rotation.
webhooks/troubleshooting.mdx Triage guide for common webhook failures.
Comments suppressed due to low confidence (1)

webhooks/verifying-signatures.mdx:208

  • The Go example defines func abs at the bottom but Go's standard library does not have an integer abs for int64 until very recent versions, and more importantly the snippet never registers handleWebhook with an HTTP mux nor calls http.ListenAndServe, so this example will not run as-is the way the Bun/Node/Python examples do (each of which is a complete, runnable server). Consider adding http.HandleFunc("/spectrum-webhook", handleWebhook) and http.ListenAndServe(":3000", nil) inside a main() function for parity with the other examples.
```go Go + net/http
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
	"os"
	"strconv"
	"time"
)

var secret = []byte(os.Getenv("SPECTRUM_SIGNING_SECRET"))

const toleranceSec = 5 * 60

func handleWebhook(w http.ResponseWriter, r *http.Request) {
	rawBody, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "read failed", 400)
		return
	}

	timestamp := r.Header.Get("X-Spectrum-Timestamp")
	signature := r.Header.Get("X-Spectrum-Signature")
	if timestamp == "" || signature == "" {
		http.Error(w, "missing headers", 400)
		return
	}

	ts, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		http.Error(w, "invalid timestamp", 400)
		return
	}
	if abs(time.Now().Unix()-ts) > toleranceSec {
		http.Error(w, "stale timestamp", 400)
		return
	}

	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte("v0:" + timestamp + ":" + string(rawBody)))
	expected := "v0=" + hex.EncodeToString(mac.Sum(nil))

	if !hmac.Equal([]byte(expected), []byte(signature)) {
		http.Error(w, "bad signature", 401)
		return
	}

	w.WriteHeader(200)
	w.Write([]byte("ok"))
}

func abs(x int64) int64 { if x < 0 { return -x }; return x }
</details>



---

💡 <a href="/photon-hq/docs/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.

Comment thread webhooks/overview.mdx
| **Per-project, per-URL** | Webhooks are owned by a project. A project can have many URLs; each URL is independent. |
| **Per-URL signing secret** | Every webhook gets its own 64-character signing secret, returned exactly once at registration time. Different URLs, different secrets. |
| **Every URL gets every event** | There is no per-webhook event subscription today. You branch on the `event` field (or `X-Spectrum-Event` header) in your handler. |
| **At-least-once delivery, in-order per project** | A delivery may arrive twice on retry. Dedupe on `webhookId + message.id`. There is no `Exactly-Once` guarantee. |
Comment thread webhooks/delivery.mdx

## The contract in one paragraph

Spectrum tries to deliver each event for up to four attempts within a ~6.2 second window. Each attempt has a 10-second per-request timeout. Retries happen on `5xx`, `408`, `429`, network errors, and our own timeouts; other `4xx` codes mean "give up." Successful deliveries acknowledge with any `2xx`. After the budget is exhausted the event is dropped from the worker's memory — there is no durable retry queue. Customers who care about every event must dedupe and tolerate occasional misses.
@@ -0,0 +1,313 @@
---
title: Verifying signatures
description: Confirm each delivery is genuine, unmodified, and recent — copy-paste verifier code for Node, Bun, and Python

```json
{
"succeed": true,
Comment thread webhooks/quickstart.mdx

const payload = JSON.parse(rawBody);
if (event === 'messages') {
console.log('message from', payload.message.sender.id, ':', payload.message.content);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
webhooks/troubleshooting.mdx (1)

214-218: ⚡ Quick win

Rewrite this support section in second-person voice.

This section uses first-person provider phrasing (“We…”, “We’ll…”). Please switch to second-person wording to match doc voice rules.

As per coding guidelines, "Use active voice and second person ("you") in documentation".

Also applies to: 234-234

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/troubleshooting.mdx` around lines 214 - 218, Rewrite the support
guidance from first-person to second-person voice: replace "We don't currently
expose..." with "You won't have access to a delivery log." Change procedural
steps to address the reader (e.g., "Reach out to support with the webhook id..."
→ "Contact support and provide the webhook id, the approximate UTC timestamp,
and what you observed on your side.") and change "We can confirm..." to "Support
can confirm whether the delivery left the worker, which status code was
returned, and how many retries occurred." Apply the same second-person
conversion to the related sentence at the other noted location.
webhooks/delivery.mdx (1)

68-75: ⚡ Quick win

Keep reader voice consistent (second person) in this block.

This section shifts into first-person provider voice (“we/us/our”). Please rewrite with second-person reader voice and neutral product-subject phrasing for consistency.

As per coding guidelines, "Use active voice and second person ("you") in documentation".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/delivery.mdx` around lines 68 - 75, Rewrite the block so voice is
consistently second-person and neutral product-subject; replace instances of
"we/us/our" in the table and Tip with "you/your" or neutral phrasing (e.g., "the
system" or "the server") while preserving meaning—specifically update rows for
`429 Too Many Requests`, "Any other `4xx`", "Connection refused / DNS error /
TCP reset", and "Per-attempt timeout (>10s)" and change the Tip heading and body
from "**Return `4xx` deliberately.**" and "it tells us 'stop retrying...'" to a
second-person/neutral form like "Return `4xx` deliberately." followed by "A
deliberate `400` or `401` tells you to stop retrying; returning `500` for the
same bug wastes retry budget and CPU." Ensure tone is active, uses "you" where
instructive, and keep technical guidance unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webhooks/delivery.mdx`:
- Line 10: Summary: The doc mixes a "~6.2s" retry window and a "10s per-request
timeout" as if they were a single total budget, which is contradictory. Fix:
rewrite the delivery paragraph(s) to separate the retry budget from per-request
timeouts—state that the worker makes up to four attempts with internal
backoff/retry scheduling that sums to ~6.2s of retry-delay budget, but each
individual HTTP attempt uses a 10s client-side timeout and therefore a single
attempt may take up to 10s; clarify that retries are attempted after backoff
only if the previous attempt returned one of the retryable conditions (5xx, 408,
429, network error, timeout), and that there is no durable retry queue. Apply
this clarified wording to the same phrasing elsewhere in the file (the other
occurrences noted).

In `@webhooks/events.mdx`:
- Around line 163-165: The guidance "Always returns 2xx fast" is too broad and
contradicts the signature-verification contract; update the text in
webhooks/events.mdx where that phrase appears (and the related
/webhooks/delivery section) to state that 2xx acknowledgements should be
returned only after successful signature verification, and that failed or
unauthenticated checks must not be acknowledged (return an appropriate non-2xx
like 401/4xx) while still recommending asynchronous processing after
verification.

In `@webhooks/managing-webhooks.mdx`:
- Around line 50-52: The table entry for the `409` error conflicts with the
"Rotation workflow" that states you can re-register the same URL before deleting
the old webhook and use dual-secret overlap; make the behavior consistent by
either (A) changing the rotation workflow to require deleting the old webhook
before re-registering, or (B) changing the `409` table row to exempt rotation
requests and explicitly document the API behavior that allows re-registering the
same URL when performing secret rotation (i.e., API returns success/creates a
second secret and keeps the old secret active for the overlap period). Update
the `409` table row and the "Rotation workflow / dual-secret overlap" text to
reference each other and include the exact condition required by the API (e.g.,
rotation header or parameter) so the flow in lines describing rotation is
executable and unambiguous.

In `@webhooks/troubleshooting.mdx`:
- Line 182: The sample assigns a credential-like string to the variable named
secret; replace that literal with an unmistakable placeholder (e.g.,
REPLACE_WITH_YOUR_SIGNING_SECRET or <SIGNING_SECRET>) so readers won't
accidentally copy real secrets—update the const secret declaration (variable
name: secret) to use the explicit placeholder and include a short inline comment
if desired indicating it must be replaced with the actual signing secret.

In `@webhooks/verifying-signatures.mdx`:
- Line 19: Standardize the failure status code to 401 across the recipe and
examples: update the narrative line "return `401 Unauthorized`" and change all
example responses that currently return `400` for missing/invalid/stale
timestamp cases to return `401 Unauthorized` instead; ensure every example block
and the descriptive guidance (the lines referencing missing/invalid/stale
timestamp checks) consistently uses `401` so implementations match the
documented policy.
- Line 287: Update the troubleshooting table row that currently maps "Sporadic
`bad signature`" to "Server clock skew" so the symptom matches the verifier
behavior: replace the symptom text "Sporadic `bad signature`" with a wording
like "Rejected as stale timestamp" (or "Signature rejected due to stale
timestamp") and keep the remediation (confirm NTP is running; loosen the
tolerance window) so the table reflects that clock skew causes a stale-timestamp
rejection rather than a generic bad-signature error.

---

Nitpick comments:
In `@webhooks/delivery.mdx`:
- Around line 68-75: Rewrite the block so voice is consistently second-person
and neutral product-subject; replace instances of "we/us/our" in the table and
Tip with "you/your" or neutral phrasing (e.g., "the system" or "the server")
while preserving meaning—specifically update rows for `429 Too Many Requests`,
"Any other `4xx`", "Connection refused / DNS error / TCP reset", and
"Per-attempt timeout (>10s)" and change the Tip heading and body from "**Return
`4xx` deliberately.**" and "it tells us 'stop retrying...'" to a
second-person/neutral form like "Return `4xx` deliberately." followed by "A
deliberate `400` or `401` tells you to stop retrying; returning `500` for the
same bug wastes retry budget and CPU." Ensure tone is active, uses "you" where
instructive, and keep technical guidance unchanged.

In `@webhooks/troubleshooting.mdx`:
- Around line 214-218: Rewrite the support guidance from first-person to
second-person voice: replace "We don't currently expose..." with "You won't have
access to a delivery log." Change procedural steps to address the reader (e.g.,
"Reach out to support with the webhook id..." → "Contact support and provide the
webhook id, the approximate UTC timestamp, and what you observed on your side.")
and change "We can confirm..." to "Support can confirm whether the delivery left
the worker, which status code was returned, and how many retries occurred."
Apply the same second-person conversion to the related sentence at the other
noted location.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 60b753cf-fe8b-4644-b0ae-348c7d3dbc7b

📥 Commits

Reviewing files that changed from the base of the PR and between 3e6d454 and eb7d4dc.

📒 Files selected for processing (8)
  • docs.json
  • webhooks/delivery.mdx
  • webhooks/events.mdx
  • webhooks/managing-webhooks.mdx
  • webhooks/overview.mdx
  • webhooks/quickstart.mdx
  • webhooks/troubleshooting.mdx
  • webhooks/verifying-signatures.mdx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (1)
**/*.mdx

📄 CodeRabbit inference engine (AGENTS.md)

**/*.mdx: Pages should be written as MDX files with YAML frontmatter
Use active voice and second person ("you") in documentation
Keep sentences concise with one idea per sentence in documentation
Use sentence case for headings in documentation
Bold UI elements in documentation (e.g., Click Settings)
Use code formatting for file names, commands, paths, and code references in documentation

Files:

  • webhooks/delivery.mdx
  • webhooks/quickstart.mdx
  • webhooks/managing-webhooks.mdx
  • webhooks/verifying-signatures.mdx
  • webhooks/overview.mdx
  • webhooks/events.mdx
  • webhooks/troubleshooting.mdx
🪛 Betterleaks (1.2.0)
webhooks/quickstart.mdx

[high] 68-68: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 78-78: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

webhooks/managing-webhooks.mdx

[high] 35-35: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

webhooks/troubleshooting.mdx

[high] 182-182: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 LanguageTool
webhooks/delivery.mdx

[grammar] ~10-~10: Use a hyphen to join words.
Context: ...nt for up to four attempts within a ~6.2 second window. Each attempt has a 10-sec...

(QB_NEW_EN_HYPHEN)


[grammar] ~42-~42: Ensure spelling is correct
Context: ...ross all attempts: roughly 6.2 seconds (200ms + 1s + 5s of sleeps, plus per-attempt t...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~49-~49: Ensure spelling is correct
Context: ... | 1 | none — fires immediately | | 2 | 200ms after attempt 1 ends | | 3 | 1 second a...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

webhooks/managing-webhooks.mdx

[style] ~207-~207: Consider an alternative to strengthen your wording.
Context: ...nt endpoints, so an attacker can't make further changes. 2. Rotate every webhook signing secret...

(CHANGES_ADJUSTMENTS)

webhooks/verifying-signatures.mdx

[style] ~262-~262: Consider using a different adverb to strengthen your wording.
Context: ...ithout the v0: prefix in the input) → completely different HMAC. The v0= versions bot...

(COMPLETELY_ENTIRELY)

webhooks/overview.mdx

[style] ~33-~33: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...eceives every event, independently. - **You're integrating from a serverless platfo...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

webhooks/troubleshooting.mdx

[style] ~143-~143: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ... return c.text('ok', 200); }); ``` For very small handlers (no LLM, no network), inline p...

(EN_WEAK_ADJECTIVE)

🔇 Additional comments (6)
webhooks/managing-webhooks.mdx (1)

1-49: LGTM!

Also applies to: 53-120, 145-211

docs.json (1)

145-166: LGTM!

webhooks/overview.mdx (1)

1-102: LGTM!

webhooks/quickstart.mdx (1)

1-205: LGTM!

webhooks/events.mdx (1)

1-162: LGTM!

webhooks/verifying-signatures.mdx (1)

1-18: LGTM!

Also applies to: 20-286, 288-314

Comment thread webhooks/delivery.mdx

## The contract in one paragraph

Spectrum tries to deliver each event for up to four attempts within a ~6.2 second window. Each attempt has a 10-second per-request timeout. Retries happen on `5xx`, `408`, `429`, network errors, and our own timeouts; other `4xx` codes mean "give up." Successful deliveries acknowledge with any `2xx`. After the budget is exhausted the event is dropped from the worker's memory — there is no durable retry queue. Customers who care about every event must dedupe and tolerate occasional misses.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clarify retry timing math to avoid contradictory guarantees.

The page currently combines a ~6.2s window with a 10s per-attempt timeout in a way that reads as a single total budget. That makes the contract internally inconsistent and can lead readers to design the wrong timeout/reconciliation behavior.

Proposed wording fix
-Spectrum tries to deliver each event for up to four attempts within a ~6.2 second window. Each attempt has a 10-second per-request timeout.
+Spectrum makes up to four delivery attempts per event.
+Retry backoff delays total ~6.2 seconds (200ms + 1s + 5s).
+Each attempt can run for up to 10 seconds before timing out.

-Total wall-clock budget across all attempts: roughly 6.2 seconds (200ms + 1s + 5s of sleeps, plus per-attempt time on the network).
+Total retry-delay budget across attempts is ~6.2 seconds (200ms + 1s + 5s), plus request time spent per attempt.

-| Endpoint down for >6 seconds | Dropped after 4 attempts. Event lost. |
+| Endpoint keeps failing through all 4 attempts | Dropped after attempt 4. Event lost. |

Also applies to: 42-42, 53-53, 130-130

🧰 Tools
🪛 LanguageTool

[grammar] ~10-~10: Use a hyphen to join words.
Context: ...nt for up to four attempts within a ~6.2 second window. Each attempt has a 10-sec...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/delivery.mdx` at line 10, Summary: The doc mixes a "~6.2s" retry
window and a "10s per-request timeout" as if they were a single total budget,
which is contradictory. Fix: rewrite the delivery paragraph(s) to separate the
retry budget from per-request timeouts—state that the worker makes up to four
attempts with internal backoff/retry scheduling that sums to ~6.2s of
retry-delay budget, but each individual HTTP attempt uses a 10s client-side
timeout and therefore a single attempt may take up to 10s; clarify that retries
are attempted after backoff only if the previous attempt returned one of the
retryable conditions (5xx, 408, 429, network error, timeout), and that there is
no durable retry queue. Apply this clarified wording to the same phrasing
elsewhere in the file (the other occurrences noted).

Comment thread webhooks/events.mdx
Comment on lines +163 to +165
Always returns 2xx fast
Process asynchronously after acknowledging — see /webhooks/delivery
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Qualify the “always 2xx” guidance to post-verification only.

Line 163 says “Always returns 2xx fast,” which conflicts with the signature-verification contract where failed checks should not be acknowledged as success. This wording can lead implementers to accept unauthenticated deliveries.

Suggested wording
-Always returns 2xx fast
-   Process asynchronously after acknowledging — see /webhooks/delivery
+After successful verification, return 2xx fast
+   Process asynchronously after acknowledging — failed verification should return 401 (see /webhooks/verifying-signatures)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Always returns 2xx fast
Process asynchronously after acknowledging — see /webhooks/delivery
```
After successful verification, return 2xx fast
Process asynchronously after acknowledging — failed verification should return 401 (see /webhooks/verifying-signatures)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/events.mdx` around lines 163 - 165, The guidance "Always returns 2xx
fast" is too broad and contradicts the signature-verification contract; update
the text in webhooks/events.mdx where that phrase appears (and the related
/webhooks/delivery section) to state that 2xx acknowledgements should be
returned only after successful signature verification, and that failed or
unauthenticated checks must not be acknowledged (return an appropriate non-2xx
like 401/4xx) while still recommending asynchronous processing after
verification.

Comment on lines +50 to +52
| `400` | Invalid `webhookUrl` (not HTTPS, malformed) | Use a valid `https://` URL |
| `409` | The same URL is already registered for this project | List existing webhooks, or delete the old one and re-register |
| `401` | Bad project credentials | Rotate via the CLI and try again |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the duplicate-URL vs rotation-flow contradiction.

The doc says re-registering the same URL returns 409 (Line 51), but the rotation workflow requires registering the same URL before deleting the old webhook (Lines 127-132), and then recommends dual-secret overlap (Lines 141-143). Both cannot be true at once. Please align these sections so the operational flow is actually executable.

Also applies to: 121-143

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/managing-webhooks.mdx` around lines 50 - 52, The table entry for the
`409` error conflicts with the "Rotation workflow" that states you can
re-register the same URL before deleting the old webhook and use dual-secret
overlap; make the behavior consistent by either (A) changing the rotation
workflow to require deleting the old webhook before re-registering, or (B)
changing the `409` table row to exempt rotation requests and explicitly document
the API behavior that allows re-registering the same URL when performing secret
rotation (i.e., API returns success/creates a second secret and keeps the old
secret active for the overlap period). Update the `409` table row and the
"Rotation workflow / dual-secret overlap" text to reference each other and
include the exact condition required by the API (e.g., rotation header or
parameter) so the flow in lines describing rotation is executable and
unambiguous.

```ts test-verify.ts
import { createHmac } from 'node:crypto';

const secret = 'a3f8e29b...5c7e9b2d';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use an unmistakable placeholder for the signing secret in the sample.

The current value looks credential-like. Using an explicit placeholder reduces copy/paste misuse and false secret-leak confusion.

Suggested edit
-const secret = 'a3f8e29b...5c7e9b2d';
+// Test placeholder only — never use a production secret in local fixtures.
+const secret = '<WEBHOOK_SIGNING_SECRET>';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const secret = 'a3f8e29b...5c7e9b2d';
// Test placeholder only — never use a production secret in local fixtures.
const secret = '<WEBHOOK_SIGNING_SECRET>';
🧰 Tools
🪛 Betterleaks (1.2.0)

[high] 182-182: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/troubleshooting.mdx` at line 182, The sample assigns a
credential-like string to the variable named secret; replace that literal with
an unmistakable placeholder (e.g., REPLACE_WITH_YOUR_SIGNING_SECRET or
<SIGNING_SECRET>) so readers won't accidentally copy real secrets—update the
const secret declaration (variable name: secret) to use the explicit placeholder
and include a short inline comment if desired indicating it must be replaced
with the actual signing secret.

3. **Recompute the HMAC** locally: `HMAC-SHA256(signingSecret, "v0:" + timestamp + ":" + rawBody)`.
4. **Compare in constant time** against the `X-Spectrum-Signature` header.

If any check fails, return `401 Unauthorized` and stop processing.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make failure status code guidance consistent across recipe and examples.

Line 19 says every failed check returns 401, but the examples return 400 for missing/invalid/stale timestamp cases. Pick one policy and document it consistently so implementations don’t diverge.

Also applies to: 43-47, 86-91, 133-140, 180-191

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/verifying-signatures.mdx` at line 19, Standardize the failure status
code to 401 across the recipe and examples: update the narrative line "return
`401 Unauthorized`" and change all example responses that currently return `400`
for missing/invalid/stale timestamp cases to return `401 Unauthorized` instead;
ensure every example block and the descriptive guidance (the lines referencing
missing/invalid/stale timestamp checks) consistently uses `401` so
implementations match the documented policy.

| Symptom | Cause | Fix |
| --- | --- | --- |
| Every request returns `bad signature` | Body was parsed and re-serialized before hashing | Capture the raw body bytes first |
| Sporadic `bad signature` | Server clock skew | Confirm NTP is running; loosen the tolerance window if you're on a constrained host |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the clock-skew troubleshooting symptom.

Line 287 maps clock skew to bad signature, but your verifier flow rejects skew as stale timestamp. Update the symptom to match the documented behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webhooks/verifying-signatures.mdx` at line 287, Update the troubleshooting
table row that currently maps "Sporadic `bad signature`" to "Server clock skew"
so the symptom matches the verifier behavior: replace the symptom text "Sporadic
`bad signature`" with a wording like "Rejected as stale timestamp" (or
"Signature rejected due to stale timestamp") and keep the remediation (confirm
NTP is running; loosen the tolerance window) so the table reflects that clock
skew causes a stale-timestamp rejection rather than a generic bad-signature
error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants