docs/framework/endpoint.mdx
Novu Framework requires a single HTTP endpoint (/api/novu or similar) to be exposed by your application. This endpoint is used to receive events from our Worker Engine.
You can view the Bridge Endpoint as a webhook endpoint that Novu will call when it needs to retrieve contextual information for a given subscriber and notification.
Using the npx novu init command creates a Bridge application for you with a Bridge Endpoint ready to go.
serve functionWe offer framework specific wrappers in form of an exported serve function that abstracts away:
GET, POST, PUT and OPTIONS requestsCurrently, we offer serve functions for the following frameworks:
When Novu calls your bridge endpoint to execute a framework step, transient failures are retried automatically before the step is marked as failed.
Novu retries up to 3 times with exponential backoff (2^attemptCount × 500ms):
| Retry | Delay |
|---|---|
| 1st | 1 second |
| 2nd | 2 seconds |
| 3rd | 4 seconds |
Retries are triggered for these HTTP status codes: 408, 429, 500, 503, 504, 521, 522, and 524.
Retries are also triggered for transient network error codes such as EAI_AGAIN, ECONNREFUSED, ECONNRESET, ETIMEDOUT, and ENOTFOUND.
Other responses — for example most 4xx errors outside 408 and 429 — are not retried. Each bridge request has a 5 second timeout.
serve functionIf we currently don't support your framework, you can write a custom serve function like the following example:
import { type Request, type Response } from 'express';
import { NovuRequestHandler, ServeHandlerOptions } from '@novu/framework';
export const serve = (options: ServeHandlerOptions) => {
const requestHandler = new NovuRequestHandler({
frameworkName: 'express',
...options,
handler: (incomingRequest: Request, response: Response) => ({
method: () => incomingRequest.method,
headers: (key) => {
const header = incomingRequest.headers[key];
return Array.isArray(header) ? header[0] : header;
},
queryString: (key) => {
const qs = incomingRequest.query[key];
return Array.isArray(qs) ? qs[0] : qs;
},
body: () => incomingRequest.body,
url: () =>
new URL(incomingRequest.url, `https://${incomingRequest.headers.get('host') || ''}`),
transformResponse: ({ body, headers, status }) => {
Object.entries(headers).forEach(([headerName, headerValue]) => {
response.setHeader(headerName, headerValue);
});
return response.status(status).send(body);
},
}),
});
return requestHandler.createHandler();
};