Posthook with Next.js
Schedule durable timers from Next.js API routes and server actions. No cron, no workers, no infrastructure — just an API call and a handler.
Serverless functions are stateless. You cannot use setTimeout, node-schedule, or in-process workers in a Next.js app deployed to Vercel or any serverless environment — the function exits after the response and any timers are lost.
Posthook provides the scheduling layer that serverless functions cannot. Schedule a hook from any server-side code — API route, server action, middleware — and receive the delivery at a Next.js API route when the time arrives.
Install
npm install @posthook/node
Create a shared client:
// lib/posthook.ts
import Posthook from '@posthook/node';
export const posthook = new Posthook(process.env.POSTHOOK_API_KEY, {
signingKey: process.env.POSTHOOK_SIGNING_KEY,
});
Schedule a hook
When a user signs up, schedule an onboarding reminder for 48 hours later:
// app/api/signup/route.ts
import { posthook } from '@/lib/posthook';
export async function POST(req: Request) {
const { userId, email } = await req.json();
await db.createUser({ userId, email });
const hook = await posthook.hooks.schedule({
path: '/api/webhooks/onboarding-reminder',
postIn: '48h',
data: { userId },
});
// Optionally store hook.id to cancel later
await db.updateUser(userId, { onboardingHookId: hook.id });
return Response.json({ ok: true });
}
The hook is persisted by Posthook. It survives deploys, restarts, and scaling events. When the time arrives, Posthook delivers the payload to your endpoint.
Receive the delivery
The handler is a standard Next.js API route. It verifies the signature, checks state, and decides whether to act:
// app/api/webhooks/onboarding-reminder/route.ts
import { posthook } from '@/lib/posthook';
export async function POST(req: Request) {
const body = await req.text();
let delivery;
try {
delivery = posthook.signatures.parseDelivery(body, req.headers);
} catch {
return new Response('Invalid signature', { status: 401 });
}
const user = await db.getUser(delivery.data.userId);
if (user.onboardingCompleted) {
return Response.json({ status: 'skipped' });
}
await sendOnboardingReminder(user);
return Response.json({ status: 'sent' });
}
posthook.signatures.parseDeliveryverifies the request signature using the signing key from the client and returns the typed payload. If the signature is invalid or the timestamp is too old, it throws — the request is rejected before your handler logic runs.
The state check is the key pattern. Posthook guarantees at-least-once delivery. Your handler guarantees safe execution by checking state before acting. If the user already completed onboarding, the handler returns success and does nothing.
Cancellation
If the user completes onboarding before the 48-hour timer fires, the handler already handles this correctly — it checks state and skips. But you can also cancel the hook early to avoid the unnecessary delivery:
// When the user completes onboarding
await posthook.hooks.delete(user.onboardingHookId);
Cancellation is a cleanup optimization, not a correctness mechanism. The handler should be safe to run regardless of whether the hook was cancelled.
Local development
Use the Posthook CLI to forward hook deliveries to your local Next.js dev server — no deploy, no public URL:
npx posthook listen --forward http://localhost:3000
The CLI connects via WebSocket and forwards each delivery as an HTTP POST to your local server with the same headers and payload as production. It automatically accepts or fails hooks based on your handler’s response status (2xx = accept, anything else = fail and retry). Schedule hooks normally from your app — they arrive at your local endpoint.
Timezone-aware scheduling
For reminders that should arrive at a specific time in the user’s timezone:
await posthook.hooks.schedule({
path: '/api/webhooks/trial-reminder',
postAtLocal: '2026-03-15T09:00:00',
timezone: user.timezone, // e.g., 'America/New_York'
data: { userId: user.id },
});
postAtLocal handles DST transitions automatically. A reminder scheduled for 9am Eastern stays at 9am Eastern whether the clocks have changed or not.
Next steps
- Quickstart guide — full setup walkthrough
- Reminders and follow-ups — the complete pattern with DIY comparison
- Expiration and timeout checks — payment holds, trial expiry, invitation deadlines
- Pricing — free plan includes 1,000 hooks/month
Frequently asked questions
Ready to get started?
Create your free account and start scheduling hooks in minutes. No credit card required.