Six weeks after you launch, someone will send you a message: "Do you have webhooks? I want to pipe new signups into our CRM."
You have two choices. You build a native Zapier integration -- which takes a week, covers one tool, and leaves every other integration request on the backlog. Or you add outbound webhooks -- one afternoon of work that handles Zapier, Make, Slack, and any internal system your customers write.
Webhooks are how customers connect your SaaS to their stack without asking you first. If your product does anything worth reacting to -- a new user signed up, a subscription changed, a file was processed -- webhooks let customers automate around those events without waiting for you.
There are two kinds of webhooks, and they flow in opposite directions.
Inbound webhooks are what Stripe sends you: Stripe posts an event to your server when a customer pays. You probably already handle these if you have billing in your app.
Outbound webhooks are the opposite: your SaaS posts an event to your customer's server when something happens in your app. The customer registers a URL, you deliver events to it, and their system reacts.
This post is about outbound webhooks -- the kind customers use to build integrations on top of your product.
A webhook system has three moving parts:
An endpoint registry -- a database table where customers store their webhook URLs and which events they want to receive. Each row has an endpoint URL, a secret for verification, and a list of subscribed event types.
An event dispatcher -- the part of your codebase that fires when something happens. After a user is created, after a subscription changes, after a file is processed -- you call a dispatch function, which looks up matching subscriptions and delivers the event.
A delivery log -- a record of every delivery attempt with the HTTP status, the payload, and the timestamp. This is what you show customers when they ask "did my webhook fire?" and what you use to retry failed deliveries.
None of this needs an external queue. With a Next.js app and Drizzle ORM on Neon, you can build a production-ready version using database tables you already know how to work with.
Start with fewer events than you think you need. Pick the five things customers are most likely to react to:
user.created -- someone signed upsubscription.activated -- a customer started payingsubscription.cancelled -- a customer cancelled[resource].created -- whatever the core object in your app is (a project, a report, a listing)[resource].updated -- the same object changedGive each event a stable string name and document them. Never rename them after customers start using them -- it breaks every integration silently, and you will only find out when a customer's automation stops working.
When an event fires in your app:
X-Webhook-Signature) so they can verify the payload came from you.The signature is an HMAC-SHA256 of the raw payload, keyed with the per-subscription secret you generate at registration time. The customer verifies it the same way you verify Stripe's signatures today -- both sides hash the same payload with the same key and compare.
Once you expose webhooks, your customers can connect your product to:
Every integration you would have had to build yourself becomes something customers can wire up in an afternoon.
With the Next.js SaaS boilerplate, you already have Drizzle ORM, a JWT-authenticated API layer, and a DDD-lite module structure that keeps this clean.
The webhook system lives in two modules: a webhookEndpoint module (schema, repo, service for managing customer subscriptions) and a webhookDelivery module (dispatching events and writing delivery logs). Your service layer calls webhookService.dispatch('user.created', payload) after any event worth exporting -- the rest handles itself.
For the API routes, the registration endpoint sits behind your existing auth middleware (see the authentication docs). Customers create and delete webhook subscriptions using the same API key they already have. If you have already built a customer-facing API, webhook management is a natural extension of it.
The customer who pipes your events into their CRM is not leaving. The customer who has not connected your product to anything yet has no switching cost at all.
Webhooks are one of those features that feel like a nice-to-have until your first enterprise deal requires them, at which point you are retrofitting them into a codebase that was never designed for it. Adding them early -- when the module structure is clean and the event surface is small -- takes a weekend. Adding them after 50 services each trigger events takes much longer.
Ready to ship a Next.js SaaS that customers can actually integrate with? Get the boilerplate and start with auth, payments, email, and a clean architecture that makes webhook delivery a feature, not a refactor.