# feat.press webhooks

feat.press sends signed JSON to the HTTPS endpoint you configure on an app product when entitlements change.

## Headers

- `feat-signature: t=<unix_seconds>,v1=<hex>`
- `feat-event-id`: idempotency id (same as payload `id`)

## Verification

Compute HMAC-SHA256 of the unix timestamp, a period, and the raw body (`${t}.${rawBody}`) with your webhook secret and compare to `v1` in constant time. Reject timestamps older than 300 seconds with HTTP 400. Duplicates must return 200 without side effects.

feat.press retries a non-2xx response up to 3 times. Each attempt times out after 8 seconds. User agent: `feat-webhooks/1.0`.

## feat.press SDK

Official SDK: `npm i @feat-press/node`. Mount `createWebhookRoute`. Replay a signed sample with `npx feat-webhook replay`.

## Event types

`webhook.test`, `entitlement.activated`, `entitlement.renewed`, `entitlement.updated`, `entitlement.cancellation_scheduled`, `entitlement.past_due`, `entitlement.expired`, `entitlement.revoked`.

Payload shape: `{ id, type, apiVersion, createdAt, data }`. `apiVersion` is `2026-07-18`. `data` includes `entitlementId`, `orderId`, `productId`, `buyerEmail`, `status`, `licenseKeyLast4`, `stripeSubscriptionId`, `tierId`, `tierName`, `billingInterval`, `currentPeriodEnd`, `cancelAtPeriodEnd`, `cancellationRequestedAt`, `canceledAt`, and `expiresAt`.

Index: https://www.feat.press/developers
