docs/examples: webhook defaults, SDK node demo, API path fixes#802
docs/examples: webhook defaults, SDK node demo, API path fixes#802
Conversation
Document v0=<hex> signing over raw body, x-outpost-timestamp in a separate header, and link to legacy template env vars. Update migration guide snippet to SDK tenants/destinations calls. Made-with: Cursor
Default Outpost signs raw body with v0=<hex>; keep optional legacy verifier for v0.11-style templates. Modernize overview curl samples and remove dated portal copy. Made-with: Cursor
Tenant-scoped topics endpoint was removed (v0.13); use global topics list. Made-with: Cursor
Replace axios OutspotClient with the official SDK (exact 0.9.2) so routes and types track releases deliberately. Add npm run typecheck and tsconfig bundler resolution for tsc --noEmit. Made-with: Cursor
events.list and tenants.list return PageIterator payloads shaped as
{ result: { models } }; access result.models for TypeScript.
Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Aligns Outpost documentation and example code with current Outpost webhook-signing defaults and the official TypeScript SDK, while fixing stale API paths and correcting SDK pagination usage in TypeScript examples/demos.
Changes:
- Updates webhook signature documentation to reflect v0.12+ default templates (raw body signing and
v0=<hex>header format). - Migrates the Node.js/TypeScript migration demo from a custom Axios client to the official
@hookdeck/outpost-sdk(pinned) and adds atypecheckscript. - Fixes stale API/example paths and corrects TypeScript usage of paginated SDK responses (
page.result.models).
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/sdk-typescript/auth.ts | Fixes tenant listing example to read items from result.models on the first page. |
| examples/demos/overview/README.md | Fixes curl examples to include http:// and updates portal description text. |
| examples/demos/nodejs/tsconfig.json | Adjusts TS module settings for the ESM demo + SDK usage/typechecking. |
| examples/demos/nodejs/src/verify-signature.ts | Updates signature verification to match v0.12+ defaults and adds legacy verifier. |
| examples/demos/nodejs/src/publish-api.ts | Switches publishing flow to the official SDK APIs and request shapes. |
| examples/demos/nodejs/src/portal-urls.ts | Switches portal URL fetching to SDK and uses redirectUrl. |
| examples/demos/nodejs/src/migrate.ts | Updates migration flow to SDK endpoints and request bodies. |
| examples/demos/nodejs/src/lib/outpost.ts | Replaces custom client with Outpost SDK initialization. |
| examples/demos/nodejs/src/lib/outpost-client.ts | Removes the legacy Axios-based wrapper. |
| examples/demos/nodejs/src/healthz.ts | Uses SDK health check and handles environments where it may 404. |
| examples/demos/nodejs/src/create-tenant.ts | Uses SDK tenant upsert + portal URL retrieval. |
| examples/demos/nodejs/README.md | Documents SDK usage and exact version pinning rationale. |
| examples/demos/nodejs/package.json | Pins @hookdeck/outpost-sdk@0.9.2, removes Axios, adds typecheck. |
| examples/demos/nodejs/package-lock.json | Lockfile update reflecting SDK adoption and dependency changes. |
| examples/demos/dashboard-integration/src/lib/outpost.ts | Fixes event listing to read from eventsPage.result.models. |
| examples/demos/dashboard-integration/package-lock.json | Updates embedded SDK version reference and dev dependency resolution. |
| examples/azure/diagnostics.sh | Updates diagnostics topics endpoint to /api/v1/topics. |
| docs/pages/guides/migrate-to-outpost.mdx | Updates migration guide snippet and webhook signature header description. |
| docs/pages/destinations/webhook.mdx | Updates signature examples and verification guidance to match v0.12+ defaults. |
Files not reviewed (2)
- examples/demos/dashboard-integration/package-lock.json: Language not supported
- examples/demos/nodejs/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return listed.some( | ||
| (sig) => | ||
| sig.length === expected.length && | ||
| crypto.timingSafeEqual( | ||
| Buffer.from(sig, "utf8"), | ||
| Buffer.from(expected, "utf8") | ||
| ) |
There was a problem hiding this comment.
crypto.timingSafeEqual(Buffer.from(sig, 'utf8'), ...) can throw if the two Buffers differ in byte length (string length check doesn’t guarantee equal UTF-8 byte length). Since sig comes from an HTTP header, a malformed/non-ASCII value could crash verification. Decode/normalize signatures first (e.g., treat them as hex/base64 per configured encoding) and ensure buffer lengths match (or catch and treat as non-match) before calling timingSafeEqual.
| function verifyWebhookSignature( | ||
| requestBody: string, | ||
| rawBody: string, | ||
| signatureHeader: string, | ||
| secret: string | ||
| ): boolean { | ||
| // Parse the signature header (assumed format: t=<timestamp>,v0=<signature>) | ||
| const parts = signatureHeader.split(","); | ||
| const timestampPart = parts.find((part) => part.startsWith("t=")); | ||
| const signaturePart = parts.find((part) => part.startsWith("v0=")); | ||
|
|
||
| console.log(`signaturePart: ${signaturePart}`); | ||
| console.log(`timestampPart: ${timestampPart}`); | ||
|
|
||
| if (!timestampPart || !signaturePart) { | ||
| return false; // Malformed signature header | ||
| const trimmed = signatureHeader.trim(); | ||
| if (!trimmed.startsWith("v0=")) { | ||
| return false; | ||
| } | ||
|
|
||
| const timestamp = timestampPart.split("=")[1]; | ||
| const expectedSignature = signaturePart.split("=")[1]; | ||
|
|
||
| // Create the string to sign | ||
| const data = `${timestamp}.${requestBody}`; | ||
|
|
||
| // Compute the HMAC SHA-256 signature | ||
| const hmac = crypto.createHmac("sha256", secret); | ||
| hmac.update(data); | ||
| const computedSignature = hmac.digest("hex"); | ||
|
|
||
| // Compare the computed signature with the expected one | ||
| console.log(`comparing: \n${computedSignature}\n${expectedSignature}`); | ||
|
|
||
| return crypto.timingSafeEqual( | ||
| Buffer.from(computedSignature, "utf8"), | ||
| Buffer.from(expectedSignature, "utf8") | ||
| const listed = trimmed.slice("v0=".length).split(","); | ||
| const expected = crypto | ||
| .createHmac("sha256", secret) | ||
| .update(rawBody) | ||
| .digest("hex"); |
There was a problem hiding this comment.
The verifier functions accept rawBody: string and pass it to hmac.update(rawBody), but the docs in this repo emphasize signatures are computed over the raw request bytes (before JSON parsing). Using a string hardcodes UTF-8 encoding and can produce mismatches for non-UTF8 payloads or when the framework gives you a Buffer/Uint8Array. Consider accepting Uint8Array/Buffer (or string | Uint8Array) and updating the HMAC with the raw bytes exactly as received.
Summary
Brings webhook docs and examples in line with current default Outpost behavior (v0.12+ signature templates), fixes a few stale API/example paths, migrates the Node migration demo to the official TypeScript SDK, and corrects TypeScript usage of paginated SDK responses.
What we checked
go test ./... -shortnpm run typecheck,npm run verify(runsverify-signature.tswith default + legacy HMAC cases)examples/sdk-typescriptnpx tsc --noEmitexamples/demos/dashboard-integrationnpx tsc --noEmit@hookdeck/outpost-sdk@0.9.2from npm;npm install+ same checks aboveFindings (not obvious from the diff alone)
v0=<hex>;x-outpost-timestampis still sent by default as its own header, but it is not part of the default signed string unless operators restore the old templates (see v0.12 upgrade guide). Docs previously mixed “timestamp in signature header” language with the current env defaults table.OutpostClientused pre–v0.12 paths (/${tenantId}); a live server would 404. The SDK removes that class of drift.GET /api/v1/tenants/:id/topicsno longer exists (v0.13); Azure diagnostics now callsGET /api/v1/topics(the script previously only warned on non-200, so behavior was easy to miss).events.list/tenants.listreturn aPageIteratorwhose first page isListEventsResponse/ListTenantsResponsewith items underresult.models, notmodelson the iterator root—TypeScript was wrong until fixed.file:for local iteration).Commits
docs:webhook + migration guideexamples:verify-signature + overview READMEexamples(azure):diagnostics topics URLexamples(node):SDK + pin + typecheck + remove custom clientexamples:paginationresult.modelsfixes