Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions docs/pages/destinations/webhook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Content-Type: application/json
x-outpost-id: evt_123
x-outpost-topic: orders
x-outpost-timestamp: 1704067200
x-outpost-signature: t=1704067200,v0=abc123...
x-outpost-signature: v0=abc123...
x-outpost-source: checkout-service

{"order_id": "123", "status": "created"}
Expand Down Expand Up @@ -135,22 +135,28 @@ Outpost adds headers using the configured prefix (default `x-outpost-`):

### Signature

The signature is computed over the timestamp and request body:
By default the signed payload is the **raw request body** (see `DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE`).

```
signature = HMAC-SHA256(secret, "${timestamp}.${body}")
signature = HMAC-SHA256(secret, body)
```

The signature header value follows the format: `t=${timestamp},v0=${signature}`
The signature header value follows the format: `v0=${signature}`. During secret rotation, Outpost may send several comma-separated digests: `v0=${sig1},${sig2}`. Compare your computed digest to any of the values using a constant-time check.

The event time is in the `x-outpost-timestamp` header (Unix seconds), not embedded in the signature header.

:::note[Upgrading from v0.11]
The previous defaults signed `${timestamp}.${body}` and used `t=${timestamp},v0=${signature}`. See the [v0.12 upgrade guide](/docs/guides/upgrade-v0.12#webhook-signature-defaults) to align receivers or restore the old templates.
:::

### Verifying Signatures

To verify webhook requests:

1. Extract the timestamp and signature from the `x-outpost-signature` header
2. Compute the expected signature using your secret
3. Compare signatures using a constant-time comparison
4. Optionally, reject requests with old timestamps to prevent replay attacks
1. Read the raw body **before** JSON parsing (the digest is over exact bytes).
2. Parse `v0=` from the `x-outpost-signature` header; split any comma-separated digests if present.
3. Compute `HMAC-SHA256(secret, rawBody)` with the configured encoding (default hex) and compare.
4. Optionally, use `x-outpost-timestamp` and reject stale requests to limit replay attacks.

### Operator Configuration

Expand Down
13 changes: 6 additions & 7 deletions docs/pages/guides/migrate-to-outpost.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Webhooks are delivered via HTTP `POST` requests.
### Webhook Event HTTP Headers

- `x-outpost-event-id`: A unique identifier for the event within the Outpost installation
- `x-outpost-signature`: The signature generated using the webhook secret (e.g., `t=<timestamp>,v0=<signature>`).
- `x-outpost-signature`: HMAC of the raw body (default `v0=<hex>`; operators may customize templates — see [Webhook destination](/docs/destinations/webhook#signature)).
- `x-outpost-timestamp`: A Unix timestamp representing the time the event was generated
- `x-outpost-topic`: The topic of the event. For example, `user.created`.

Expand Down Expand Up @@ -140,7 +140,7 @@ The following diagram shows the Outpost schema. You can connect to the database
A migration script may look something like this:

```ts
// Outpost API wrapper
// Outpost TypeScript SDK (see examples/demos/nodejs)
import outpost from "./outpost";

// Database wrapper
Expand All @@ -163,7 +163,7 @@ const migrateOrganizations = async () => {
const migratedOrgIds: string[] = [];
const organizations = db.getOrganizations();
for (const organization of organizations) {
await outpost.registerTenant(organization.id);
await outpost.tenants.upsert(organization.id);
migratedOrgIds.push(organization.id);
}
return migratedOrgIds;
Expand All @@ -172,12 +172,11 @@ const migrateOrganizations = async () => {
const migrateSubscriptions = async (organizationId: string) => {
const subscriptions = db.getSubscriptions(organizationId);
for (const subscription of subscriptions) {
await outpost.createDestination({
tenant_id: organizationId,
await outpost.destinations.create(organizationId, {
type: "webhook",
url: subscription.url,
config: { url: subscription.url },
topics: subscription.topics,
secret: subscription.secret,
credentials: { secret: subscription.secret },
});
}
};
Expand Down
2 changes: 1 addition & 1 deletion examples/azure/diagnostics.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ run_api_tests() {
echo " -> ✅ Tenant created."

echo " (Checking configured topics...)"
topics_response=$(curl -s -w "\n%{http_code}" -X GET "$base_url/api/v1/tenants/$TENANT_ID/topics" \
topics_response=$(curl -s -w "\n%{http_code}" -X GET "$base_url/api/v1/topics" \
-H "Authorization: Bearer $API_KEY")

topics_http_code=$(echo "$topics_response" | tail -n1)
Expand Down
4 changes: 2 additions & 2 deletions examples/demos/dashboard-integration/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions examples/demos/dashboard-integration/src/lib/outpost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export async function getTenantOverview(tenantId: string) {
: [];
logger.debug(`Destinations found`, { tenantId, count: destinations.length });

// Get recent events (SDK v0.13: list returns { models, pagination })
// Get recent events (PageIterator<ListEventsResponse>; items live under result.models)
let recentEvents: any[] = [];
try {
const eventsResponse = await outpost.events.list({ tenantId });
const models = eventsResponse?.models ?? [];
const eventsPage = await outpost.events.list({ tenantId });
const models = eventsPage.result?.models ?? [];
recentEvents = models.slice(0, 10);
logger.debug(`Events found`, { tenantId, count: recentEvents.length });
} catch (error) {
Expand Down
2 changes: 2 additions & 0 deletions examples/demos/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Examples for Node.js / TypeScript

Admin API calls use the official [`@hookdeck/outpost-sdk`](https://www.npmjs.com/package/@hookdeck/outpost-sdk). The version is **pinned exactly** in `package.json` so new SDK releases do not change this demo unexpectedly. Bump the pin when you intentionally adopt a newer SDK (run `npm install` and fix any API drift).

Create a `.env`:

```
Expand Down
Loading
Loading