@hookdeck/vercel
v0.3.1
Published
The Hookdeck Vercel Middleware adds the ability to authenticate, delay, filter, queue, throttle, and retry asynchronous HTTP requests (e.g., webhooks) made to a Vercel application. The use cases for this include managing webhooks from API providers such a
Downloads
226
Readme
Hookdeck Vercel Middleware
The Hookdeck Vercel Middleware adds the ability to authenticate, delay, filter, queue, throttle, and retry asynchronous HTTP requests (e.g., webhooks) made to a Vercel application. The use cases for this include managing webhooks from API providers such as Stripe, Shopify, and Twilio, or when building asynchronous APIs.
Get Started
Before you begin:
- Create a Vercel account and a project.
- Signup for a Hookdeck account and create your Hookdeck project.
- Get the Hookdeck API key and Signing Secret from your project secrets.
- Add
HOOKDECK_API_KEY
andHOOKDECK_SIGNING_SECRET
(optional but recommended) as environment variables for your Vercel project.
Install the Hookdeck Vercel package:
npm i @hookdeck/vercel
Once installed, package a
hookdeck.config.js
file is created at the root of your project. Also, the commandhookdeck-vercel deploy
is appended to theprebuild
script of yourpackage.json
.
Ensure the match
key in hookdeck.config.js
matches the route you want the middleware to intercept:
const { RetryStrategy, DestinationRateLimitPeriod } = require('@hookdeck/sdk/api');
/** @type {import("@hookdeck/vercel").HookdeckConfig} */
const hookdeckConfig = {
match: {
'/api/webhooks': {
retry: {
strategy: RetryStrategy.Linear,
count: 5,
interval: 1 * 60 * 1000, // in milliseconds
},
rate: {
limit: 10,
period: DestinationRateLimitPeriod.Second,
},
},
},
};
module.exports = hookdeckConfig;
This example configures a retry strategy with a linear back-off and a delivery rate limit of 10 requests per second.
ℹ️ The Hookdeck Vercel Middleware uses
VERCEL_BRANCH_URL
by default. If you are not deploying your application via source control, sethookdeckConfig.vercel_url
to a different URL such asprocess.env.VERCEL_URL
.
Update middleware.ts
(or middleware.js
) to add the Hookdeck Vercel Middleware and ensure config.matcher
has the same (or broader) value as you have in hookdeck.config.js
(e.g., /api/webhooks
):
import { withHookdeck } from '@hookdeck/vercel';
import hookdeckConfig from './hookdeck.config';
import { NextResponse } from 'next/server';
export const config = {
matcher: '/api/webhooks',
};
function middleware(request: Request) {
// ... existing or additional your middleware logic
NextResponse.next();
}
// wrap the middleware with Hookdeck wrapper
export default withHookdeck(hookdeckConfig, middleware);
If you don't already have a route, create one. For example, app/api/webhooks/route.ts
for the /api/webhooks
endpoint in a Next.js app using the app router:
export async function POST() {
const data = { received: true };
return Response.json(data);
}
Deploy the application to Vercel.
Once the deployment has succeeded, make a request to your middleware endpoint:
curl --location 'http://your.vercel.app/api/webhooks' \
--header 'Content-Type: application/json' \
--data '{
"test": "value"
}'
Check the Vercel logs to see that the middleware is processing the events:
Check the Hookdeck logs to see that the requests are being handled and the events are being processed and delivered:
Configuration
Environment Variables
Environment variables used by the middleware. Get the values from the Hookdeck project secrets.
HOOKDECK_API_KEY
: The Hookdeck project API Key used to manage the connections within your project.HOOKDECK_SIGNING_SECRET
: Optional but recommended. Used to check the signature of the inbound HTTP request when received from Hookdeck. See webhook signature verification. Get the value from the Hookdeck project secrets.
You can also set these values programmatically within hookdeck.config.js
.
middleware.ts
The Vercel Edge Middleware must be updated as follows:
- Update the exported
config.matcher
to match the route for the Hookdeck Vercel Middleware - Import and use
withHookdeck
middleware wrapper
// add Hookdeck imports
import { withHookdeck } from '@hookdeck/vercel';
import hookdeckConfig from './hookdeck.config';
export const config = {
matcher: 'path/to/match',
};
// the middleware is not exported anymore
function middleware(request: Request) {
// ... your middleware logic
// return `NextResponse.next()` or `next()` to manage the request with Hookdeck
}
// wrap the middleware with Hookdeck wrapper
export default withHookdeck(hookdeckConfig, middleware);
hookdeck.config.js
The minimum configuration is the following, with the match
containing an object with a matching path (path/to/match
) as the key. This value should be the same as the value exported via config.matcher
in middleware.ts
.
/** @type {import("@hookdeck/vercel").HookdeckConfig} */
const hookdeckConfig = {
match: {
'path/to/match': {},
},
};
module.exports = hookdeckConfig;
IMPORTANT: Ensure
config.matcher
in yourmiddleware
includes the routes specified in thehookdeck.config.js
match
fields. Only routes that match both expressions will trigger the Hookdeck functionality.
Full configuration options:
api_key
: The Hookdeck project API Key used to manage the connections within your project. This config value will override theHOOKDECK_API_KEY
environment variable. Get the value from the Hookdeck project secrets.signing_secret
: Used to check the signature of the inbound HTTP request when it is received from Hookdeck. This config value will override theHOOKDECK_SIGNING_SECRET
environment variable. See webhook signature verification. Get the value from the Hookdeck project secrets.vercel_url
: The Vercel URL that receives the requests. If not specified, the url stored in env varVERCEL_BRANCH_URL
will be used.match
: a key-value map of paths for routes and the configuration for each of those routes.[path]
- the matching string or regex that will be compared or tested against the pathname of the URL that triggered the middleware. If there is more than one match, then the request is sent to every matching configuration.retry
: use the values specified in the Retry documentation to configure Hookdeck's retry strategy.delay
: the number of milliseconds to hold the event when it arrives at Hookdeck.filters
: specify different filters to exclude some events from forwarding. Use the syntax specified in the Filter documentation.rate
: set the delivery rate to be used for the outcoming traffic. Check the syntax in therate_limit_period
key in the Destination documentation.verification
: the inbound (source) verification mechanism to use. Check all possible values and syntax in the Source documentation.custom_response
: the custom response to send back the webhook origin. Check the syntax in the Source documentation.
Here's an example with all the configuration options:
const {
RetryStrategy,
DestinationRateLimitPeriod,
SourceCustomResponseContentType,
} = require('@hookdeck/sdk/api');
/** @type {import("@hookdeck/vercel").HookdeckConfig} */
const hookdeckConfig = {
// vercel_url: '', // optional. Uses `VERCEL_BRANCH_URL` env var as default.
match: {
'/api/webhooks': {
// all these fields are optional
retry: {
strategy: RetryStrategy.Linear,
count: 5,
interval: 1 * 60 * 1000, // in milliseconds
},
delay: 1 * 60 * 1000, // in milliseconds
filters: [
{
headers: {
'x-my-header': 'my-value',
},
body: {},
query: {},
path: {},
},
],
rate: {
limit: 10,
period: DestinationRateLimitPeriod.Minute,
},
verification: {
type: 'API_KEY',
configs: {
header_key: 'x-my-api-key',
api_key: 'this-is-my-token',
},
},
custom_response: {
content_type: SourceCustomResponseContentType.Json,
body: '{"message": "Vercel handled the webhook using Hookdeck"}',
},
},
},
};
module.exports = hookdeckConfig;
This includes request delay, retry, and a rate of delivery:
const { RetryStrategy, DestinationRateLimitPeriod } = require('@hookdeck/sdk/api');
/** @type {import("@hookdeck/vercel").HookdeckConfig} */
const hookdeckConfig = {
vercel_url: 'https://my-vercel-project-eta-five-30.vercel.app',
match: {
'/api/webhook': {
name: 'my-webhook-source-name',
retry: {
strategy: RetryStrategy.Linear,
count: 5,
interval: 15000, // in ms
},
delay: 30000, // in ms
rate: {
limit: 100,
period: DestinationRateLimitPeriod.Minute,
},
},
},
};
module.exports = hookdeckConfig;
Considerations and Limitations
Removing the Middleware and Going Directly to Hookdeck
The Hookdeck Vercel middleware adds a hop to every process request, so if milliseconds are a factor in processing requests, you may want to use Hookdeck directly and not use the middleware.
With the Hookdeck Vercel Middleware:
- Request to Vercel URL
- Forward request to Hookdeck
- Request back to Vercel URL (which the middleware passes through)
Without the Hookdeck Vercel Middleware:
- Request to Hookdeck Source URL
- Request to Vercel URL
You can remove the middleware by uninstalling the package and removing any configuration and directly using the Hookdeck Source URL where you previously used the Vercel URL, for example, as your Stripe or Shopify webhook URL.
Parallel Matching
If you have multiple entries in the config file with the same match
path, be aware that the middleware will send the request via fetch
call once per match and will try to do that in parallel. This heavy use is not a common case, but please check Edge Middleware Limitations if you are in this scenario.
How the Hookdeck Vercel Middleware Works
The @hookdeck/vercel
package is supported in the Vercel Edge Middleware and executes before a request is processed on your site. This way, the request can be forwarded to Hookdeck and then received again by your specified endpoint, but with all the extra features you may need from Hookdeck, such as queuing, filters, and retry strategies.
This Hookdeck Vercel Middleware package is used in two stages:
Deploy/Build
During deployment, a `prebuild`` hook checks a configuration file and dynamically creates and configures a connection in Hookdeck:
For example, the following hookdeck.config.js
:
/** @type {import("@hookdeck/vercel").HookdeckConfig} */
const hookdeckConfig = {
vercel_url: 'https://hookdeck-vercel-example.vercel.app/',
match: {
'/api/webhooks': {},
},
};
module.exports = hookdeckConfig;
Results in something like the following connection being created in Hookdeck:
Runtime
The package also in runtime by sending to Hookdeck the requests that match your configuration:
When your Edge Middleware is triggered (because your middleware config matches), the withHookdeck
wrapper acts as follows:
If there is no config file or none of the entries inside
hookdeck.config.js
matches the route, then yourmiddleware
function is invoked as is. If there are matches with the entries ofhookdeck.config.js
then the following can happen:The received request has not been processed by Hookdeck (yet). In this case, your
middleware
function is invoked to obtain aresponse
. If the returned value from yourmiddleware
isNextResponse.next()
ornext()
, then the request is bounced back to Hookdeck.NOTE: If you are not using
next/server
or@vercel/edge
, return a newResponse
with a headerx-middleware-next
with value"1"
if you want you want Hookdeck to manage your request.The received request comes from Hookdeck and has been processed. Then, the request is sent to the final route or URL you specified. Your
middleware
function code will not be executed this time.
Development
Build
npm run build
Release
Bump the version according to semver.
Commit the changes.
Push the changes to GitHub.
Release to NPM
npm run release
Release on GitHub.