Skip to main content

Installation

Install Hookstack SDK in your project:
npm install @hookstack/sdk
# or
yarn add @hookstack/sdk
# or
pnpm add @hookstack/sdk

Quick Setup

1. Configure Environment

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;

3. Configure Your Webhook URL

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

Common causes of signature verification failures:
  1. Incorrect HOOKSTACK_SIGNING_SECRET
  2. Modified request body (ensure raw body parsing)
  3. Missing or incorrect headers
  4. 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.