Skip to content

docs/examples: webhook defaults, SDK node demo, API path fixes#802

Open
leggetter wants to merge 5 commits intomainfrom
chore/check-examples
Open

docs/examples: webhook defaults, SDK node demo, API path fixes#802
leggetter wants to merge 5 commits intomainfrom
chore/check-examples

Conversation

@leggetter
Copy link
Copy Markdown
Collaborator

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

Area How
Go codebase/tests go test ./... -short
Node migration demo npm run typecheck, npm run verify (runs verify-signature.ts with default + legacy HMAC cases)
examples/sdk-typescript npx tsc --noEmit
examples/demos/dashboard-integration npx tsc --noEmit
SDK version for Node demo Pinned exact @hookdeck/outpost-sdk@0.9.2 from npm; npm install + same checks above

Findings (not obvious from the diff alone)

  • Default webhook signing is raw body + header v0=<hex>; x-outpost-timestamp is 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.
  • The old Node OutpostClient used pre–v0.12 paths (/${tenantId}); a live server would 404. The SDK removes that class of drift.
  • GET /api/v1/tenants/:id/topics no longer exists (v0.13); Azure diagnostics now calls GET /api/v1/topics (the script previously only warned on non-200, so behavior was easy to miss).
  • events.list / tenants.list return a PageIterator whose first page is ListEventsResponse / ListTenantsResponse with items under result.models, not models on the iterator root—TypeScript was wrong until fixed.
  • Node demo SDK pinning: using an exact npm version avoids this folder breaking on every SDK publish; bump the pin when you want to chase new APIs (other demos like dashboard still use file: for local iteration).

Commits

  1. docs: webhook + migration guide
  2. examples: verify-signature + overview README
  3. examples(azure): diagnostics topics URL
  4. examples(node): SDK + pin + typecheck + remove custom client
  5. examples: pagination result.models fixes

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
Copilot AI review requested due to automatic review settings March 31, 2026 14:38
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 31, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
outpost-docs Ready Ready Preview, Comment Mar 31, 2026 2:40pm
outpost-website Ready Ready Preview, Comment Mar 31, 2026 2:40pm

Request Review

Copy link
Copy Markdown
Contributor

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

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 a typecheck script.
  • 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.

Comment on lines +24 to +30
return listed.some(
(sig) =>
sig.length === expected.length &&
crypto.timingSafeEqual(
Buffer.from(sig, "utf8"),
Buffer.from(expected, "utf8")
)
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 8 to +22
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");
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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