Security is critical when exposing webhook endpoints. Posthook provides tools to ensure that your communication is secure and authenticated.
Keys
API Key
The API Key is used to authenticate your requests to the Posthook API (e.g., when scheduling a hook via POST /hooks). This key grants full access to your project, so keep it secret.
- Usage: Set in the
X-API-Key header.
- Exposure: Never expose this in client-side code (browsers, mobile apps). Store it securely in your backend environment variables.
Signing Key
The Signing Key is used to verify that incoming hooks to your endpoint actually came from Posthook.
- Usage: Used to compute the
Posthook-Signature and X-Ph-Signature headers included with every delivery.
- Rotation: Posthook supports zero-downtime key rotation. During the grace period,
Posthook-Signature includes signatures for both the active and retiring keys. See Key Rotation.
- Exposure: This key should also be kept secret on your backend.
Replay Attacks
To protect against replay attacks, where an attacker intercepts and resends a valid payload, verify the timestamp included with the delivery.
You can use either:
Posthook-Timestamp header (recommended) — Unix timestamp in seconds. This value is also part of the signed payload, so it cannot be tampered with.
postedAt body field — RFC 3339 timestamp.
Best Practice
Reject requests where the timestamp is too far from the current time (e.g., 5 minutes).
const TOLERANCE = 5 * 60; // 5 minutes in seconds
const timestamp = parseInt(req.headers['posthook-timestamp'], 10);
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > TOLERANCE) {
return res.status(401).send('Request too old');
}
SDK Verification (Recommended)
The official SDKs provide a parseDelivery() method that handles signature verification, timestamp validation, and replay protection in a single call. You will need your Signing Key, found in Project Settings in the Dashboard.
import { Signatures, SignatureVerificationError } from '@posthook/node';
const signatures = new Signatures(process.env.POSTHOOK_SIGNING_KEY);
app.post('/webhooks/posthook/remind', async (req, res) => {
try {
const delivery = signatures.parseDelivery(req.body, req.headers);
// delivery.data is your verified payload
await sendReminder(delivery.data.userId);
res.status(200).send('OK');
} catch (err) {
if (err instanceof SignatureVerificationError) {
return res.status(401).json({ error: err.message, code: err.code });
}
throw err;
}
});
parseDelivery() automatically rejects requests older than 5 minutes (configurable via tolerance option). No need to implement replay protection manually.
How It Works
Under the hood, the SDK verifies the Posthook-Signature header using HMAC-SHA256. Every webhook delivery includes three headers:
| Header | Example |
|---|
Posthook-Id | e5405623-2c1c-460e-9737-c884f7f59035 |
Posthook-Timestamp | 1700000000 |
Posthook-Signature | v1,abc123 v1,def456 |
The signature algorithm:
- Build the signed payload:
{Posthook-Timestamp}.{raw_body} (dot-separated).
- Compute HMAC-SHA256 using your project’s signing key.
- Hex-encode the result (lowercase, 64 characters).
- Prefix with
v1,.
During key rotation, Posthook-Signature contains two space-separated signatures (one per key). The SDK accepts the delivery if either signature matches.
Key Rotation
Posthook supports zero-downtime key rotation through a grace period:
- Before rotation —
Posthook-Signature has one v1, entry using the active signing key.
- After rotation (grace period) —
Posthook-Signature includes two v1, entries: one for the new active key and one for the retiring key. Accept the delivery if either matches.
- After grace period expires — The retiring key is excluded.
Posthook-Signature returns to a single v1, entry.
- Revoke (optional) — You can revoke the retiring key early via the API or dashboard.
The legacy X-Ph-Signature header switches to the new key immediately on rotation with no grace period. This is the primary reason to migrate to Posthook-Signature.
X-Ph-Signature is deprecated. Migrate to Posthook-Signature for dual-key support during key rotation. The SDK’s parseDelivery() verifies Posthook-Signature automatically.
X-Ph-Signature contains a hex-encoded HMAC-SHA256 of the raw request body using the active project signing key.
Key difference: X-Ph-Signature uses only the active key. When you rotate your signing key, this header immediately switches to the new key — there is no grace period. If your consumer hasn’t updated yet, verification will fail until it starts using the new key.