Installation
Install Hookstack SDK in your project:
npm install @hookstack/sdk
# or
yarn add @hookstack/sdk
# or
pnpm add @hookstack/sdk
Quick Setup
First, set up your signing secret. This is used to verify incoming webhooks:
# .env
HOOKSTACK_SIGNING_SECRET=your_signing_secret_here
2. Create Your First Webhook Handler
// app/api/webhooks/route.ts
import { NextResponse } from 'next/server';
import { verifyWebhookSignature } from '@hookstack/next';
const SIGNING_SECRET = process.env.HOOKSTACK_SIGNING_SECRET!;
export async function POST(request: Request) {
try {
const body = await request.json();
// Get signature details from headers
const signature = request.headers.get('x-hookstack-signature');
const timestamp = request.headers.get('x-hookstack-timestamp');
const version = request.headers.get('x-hookstack-version');
if (!signature || !timestamp || !version) {
return NextResponse.json(
{ error: 'Missing signature headers' },
{ status: 400 }
);
}
// Verify the webhook signature
verifyWebhookSignature({
payload: body,
signature,
timestamp,
version,
signingSecret: SIGNING_SECRET,
});
// Handle the webhook event
if (body.type === 'payment.succeeded') {
const { amount, currency } = body.data;
// Process the payment success
// Your business logic here
}
return NextResponse.json({ received: true });
} catch (err) {
console.error('Webhook error:', err);
return NextResponse.json(
{ error: 'Webhook signature verification failed' },
{ status: 401 }
);
}
}
// webhooks/handler.ts
import express from 'express';
import { verifyWebhookSignature } from '@hookstack/sdk';
const router = express.Router();
const SIGNING_SECRET = process.env.HOOKSTACK_SIGNING_SECRET!;
router.post('/webhooks', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const signature = req.headers['x-hookstack-signature'];
const timestamp = req.headers['x-hookstack-timestamp'];
const version = req.headers['x-hookstack-version'];
if (!signature || !timestamp || !version) {
return res.status(400).json({ error: 'Missing signature headers' });
}
// Verify the webhook signature
verifyWebhookSignature({
payload: req.body,
signature,
timestamp,
version,
signingSecret: SIGNING_SECRET,
});
// Handle the webhook event
const event = JSON.parse(req.body);
if (event.type === 'payment.succeeded') {
const { amount, currency } = event.data;
// Process the payment success
// Your business logic here
}
res.json({ received: true });
} catch (err) {
console.error('Webhook error:', err);
res.status(401).json({ error: 'Webhook signature verification failed' });
}
});
export default router;
// app/webhooks/route.ts
import { Hono } from 'hono';
import { verifyWebhookSignature } from '@hookstack/sdk';
const app = new Hono();
const SIGNING_SECRET = process.env.HOOKSTACK_SIGNING_SECRET!;
app.post('/webhooks', async (c) => {
try {
const body = await c.req.json();
// Get signature details from headers
const signature = c.req.header('x-hookstack-signature');
const timestamp = c.req.header('x-hookstack-timestamp');
const version = c.req.header('x-hookstack-version');
if (!signature || !timestamp || !version) {
return c.json(
{ error: 'Missing signature headers' },
400
);
}
// Verify the webhook signature
verifyWebhookSignature({
payload: body,
signature,
timestamp,
version,
signingSecret: SIGNING_SECRET,
});
// Handle the webhook event
if (body.type === 'payment.succeeded') {
const { amount, currency } = body.data;
// Process the payment success
// Your business logic here
}
return c.json({ received: true });
} catch (err) {
console.error('Webhook error:', err);
return c.json(
{ error: 'Webhook signature verification failed' },
401
);
}
});
export default app;
Create a new webhook in your Hookstack dashboard and copy the generated Webhook URL.
Set up your webhook URL in your provider’s dashboard and ensure you’ve configured the signing secret correctly:
https://hooks.hookstack.dev/v1/in/custom/abc123
Security Features
Hookstack provides robust security features out of the box:
Signature Verification
Cryptographic verification of webhook signatures using HMAC-SHA256
Timestamp Validation
Protection against replay attacks with timestamp verification
Version Control
Support for multiple signature versions for seamless upgrades
Raw Body Handling
Proper handling of raw request bodies for signature verification
Next Steps
Troubleshooting
Signature Verification Failed
Common causes of signature verification failures:
- Incorrect
HOOKSTACK_SIGNING_SECRET
- Modified request body (ensure raw body parsing)
- Missing or incorrect headers
- Clock drift between servers (check timestamp)
For Express applications, ensure you use express.raw()
middleware:
app.use('/webhooks', express.raw({ type: 'application/json' }));
Using express.json()
will modify the request body and break signature verification.
For time-intensive processing:
try {
// 1. Verify signature first
verifyWebhookSignature({
payload: req.body,
signature,
timestamp,
version,
signingSecret: SIGNING_SECRET,
});
// 2. Acknowledge receipt quickly
res.json({ received: true });
// 3. Process asynchronously
await processWebhookAsync(req.body);
} catch (err) {
// Handle errors appropriately
}
The Next.JS example provides a nice example of how to handle this when deploying to Vercel, using the waitUntil()
async processing function.