Skip to content
11 changes: 11 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@
"platform/integrations/sms/sendchamp",
"platform/integrations/sms/simpletexting",
"platform/integrations/sms/sms-central",
"platform/integrations/sms/sms-webhook",
"platform/integrations/sms/sms77",
"platform/integrations/sms/sns",
"platform/integrations/sms/telnyx",
Expand Down Expand Up @@ -1345,6 +1346,16 @@
"destination": "/platform/integrations/sms/bulk-sms",
"permanent": true
},
{
"source": "/channels/sms/generic-sms",
"destination": "/platform/integrations/sms/sms-webhook",
"permanent": true
},
{
"source": "/integrations/providers/sms/generic-sms",
"destination": "/platform/integrations/sms/sms-webhook",
"permanent": true
},
{
"source": "/platform/subscription/Headless-hooks",
"destination": "/platform/subscription/headless-hooks",
Expand Down
22 changes: 22 additions & 0 deletions docs/framework/endpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ Currently, we offer `serve` functions for the following frameworks:
- [Remix](/framework/quickstart/remix)
- [Sveltekit](/framework/quickstart/svelte)

## Retry behavior

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.

## Writing a custom `serve` function

If we currently don't support your framework, you can write a custom `serve` function like the following example:
Expand Down Expand Up @@ -78,4 +96,8 @@ export const serve = (options: ServeHandlerOptions) => {
<Accordion title="Can endpoint other than /api/novu be used?">
Yes, you can use any path you want. However, you need to make sure that the endpoint is used in the bridge url. Bridge url is made of the base url and the endpoint path. If your path is /custom-path/novu and deployed application url is https://my-app.com, then the bridge url will be https://my-app.com/custom-path/novu.
</Accordion>

<Accordion title="What happens when a bridge step request fails?">
Novu retries transient errors up to 3 times with exponential backoff before marking the step as failed. See [Retry behavior](/framework/endpoint#retry-behavior) for retried status codes, network errors, and delay timings.
</Accordion>
</AccordionGroup>
3 changes: 3 additions & 0 deletions docs/platform/inbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ You get the power of Novu's notification engine while building a user interface
<Accordion title="Does the Inbox support user notification preferences?">
Yes. The Inbox includes built-in preference management so subscribers can control which workflows and channels they receive notifications through.
</Accordion>
<Accordion title="How do I pass or update subscriber profile fields from the Inbox?">
Pass a `subscriber` object with fields such as `firstName`, `email`, `locale`, and `data`. New subscribers are created on the first session; updating an existing profile requires [HMAC encryption](/platform/inbox/prepare-for-production#secure-your-inbox-with-hmac-encryption) and `subscriberHash`.
</Accordion>
<Accordion title="What is the minimum React version required for the Inbox?">
The [`@novu/react`](/platform/sdks/react) package requires **React 18.0.0 or later**. React 19 is also supported. React 17 and earlier versions are not supported.
</Accordion>
Expand Down
12 changes: 8 additions & 4 deletions docs/platform/inbox/prepare-for-production.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ When you add the Inbox to your application, you're required to pass:
Without additional security, a malicious actor could potentially guess another subscriber's `subscriberId` and use your public `applicationIdentifier` to view that user's notifications.
</Warning>

You can prevent this by enabling HMAC (Hash-based Message Authentication Code) encryption. This process uses a _secret key_ to create a secure signature (`subscriberHash`) for each `subscriberId`. Novu then verifies this hash to ensure that requests to view a notification feed are authentic and not from an impersonator.
You can prevent this by enabling HMAC (Hash-based Message Authentication Code) encryption. This process uses a _secret key_ to create a secure signature (`subscriberHash`) for each `subscriberId`. Novu verifies this hash to authenticate feed requests and to authorize updates to subscriber profile fields (`firstName`, `email`, `locale`, `data`, and so on) passed via the `subscriber` prop. Without `subscriberHash`, new subscribers are created with those fields, but existing profiles are not updated.

Follow these steps to enable HMAC encryption.

Expand Down Expand Up @@ -231,7 +231,12 @@ const hmacHash = user?.novuSubscriberHash;

<Inbox
applicationIdentifier="YOUR_APPLICATION_IDENTIFIER"
subscriber="YOUR_SUBSCRIBER_ID"
subscriber={{
subscriberId: user.id,
firstName: user.firstName,
email: user.email,
locale: user.locale,
}}
subscriberHash={hmacHash}
/>;
```
Expand Down Expand Up @@ -266,8 +271,7 @@ const context = {
</Tabs>

<Note>
If HMAC encryption is active in In-App provider settings and `subscriberHash` along with
`subscriberId` is not provided, then Inbox will not load
If HMAC encryption is active in In-App provider settings and `subscriberHash` is not provided, the Inbox will not load. When passing a `subscriber` object, `subscriberHash` is also required to update an existing subscriber's profile fields.
</Note>

## Remove Novu branding
Expand Down
4 changes: 4 additions & 0 deletions docs/platform/inbox/setup-inbox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,7 @@ If your Novu account is in the EU region, then include the `backendUrl` and `soc
<div className="text-sm text-gray-500 text-center mt-2">
[Sign in](https://dashboard.novu.co/auth/sign-up) to get your own API keys
</div>

## Pass subscriber profile fields

Pass a `subscriber` object instead of a subscriber ID string to set profile fields such as `firstName`, `email`, `locale`, and `data` on session init. New subscribers are created automatically; updating an existing profile requires [HMAC encryption](/platform/inbox/prepare-for-production#secure-your-inbox-with-hmac-encryption) and `subscriberHash`.
21 changes: 21 additions & 0 deletions docs/platform/integrations/chat/slack.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,27 @@ After the user approves access, Novu handles the rest of the OAuth flow automati
</Step>
</Steps>

<Note>
**Alternative: handle OAuth yourself.** Instead of `generateChatOAuthUrl()`, you can run the Slack OAuth flow with your own app, use the access token with Slack's APIs directly, then register the workspace with Novu via [`channelConnections.create`](/api-reference/channel-connections/create-a-channel-connection) (or `POST /v1/channel-connections`):

```tsx
await novu.channelConnections.create({
integrationIdentifier: 'slack',
subscriberId: 'user-123', // provide subscriberId, context, or both — at least one is required
context: { tenant: 'acme' }, // scope to a tenant (required when subscriberId is omitted)
workspace: {
id: authData.team.id, // Slack workspace ID from OAuth
name: authData.team.name,
},
auth: {
accessToken: authData.access_token,
},
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.
```

Provide at least one of `subscriberId` or `context` — same as the Novu-managed OAuth flow.
</Note>

## Choose delivery destinations

After a workspace is connected, you or users decide where in Slack to send the messages. This can either be a Slack channel, to a user or an incoming webhook URLs. After the delivery location has been selected, a Slack endpoint is then created for that location.
Expand Down
23 changes: 23 additions & 0 deletions docs/platform/integrations/email/resend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,32 @@ Resend allows you to authenticate your sender identity using [Domain Authentica
- Click on the **Update** button.
- You should now be able to send notifications using Resend in Novu.

## React Email with Code Steps

<Tip>
Resend maintains [React Email](https://react.email). Use Novu [Code Steps](/platform/workflow/add-and-configure-steps/code-steps) to author templates in React Email and deliver them through your Resend integration—no dashboard HTML editor required.
</Tip>

1. Connect Resend in Novu (steps below).
2. Add an email step and switch it to **Custom Code**.
3. Publish a handler linked to your React Email template:

```bash
npx novu step publish \
--workflow your-workflow-id \
--step your-email-step-id \
--template ./emails/welcome.tsx \
--secret-key nv-sk-...
```

The CLI scaffolds a handler that calls `render()` from `@react-email/components` and returns HTML for Novu to send via Resend. See [Code Steps](/platform/workflow/add-and-configure-steps/code-steps) for the more details.

## Next Steps

<Columns cols={2}>
<Card title="Code Steps with React Email" icon="code" href="/platform/workflow/add-and-configure-steps/code-steps">
Publish React Email templates from your codebase and send them through Resend.
</Card>
<Card title="Configure bcc, cc, and reply-to" href="/platform/integrations/email#sending-email-overrides">
Learn how to configure bcc, cc, and reply-to for your email notifications using email overrides
</Card>
Expand Down
1 change: 1 addition & 0 deletions docs/platform/integrations/sms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ Here are the SMS providers that are currently supported by Novu. Select any prov
<Card title="Sendchamp" href="/platform/integrations/sms/sendchamp"> Learn how to use the Sendchamp provider to send SMS notifications using Novu.</Card>
<Card title="SimpleTexting" href="/platform/integrations/sms/simpletexting"> Learn how to use the SimpleTexting provider to send SMS notifications using Novu.</Card>
<Card title="SMS Central" href="/platform/integrations/sms/sms-central"> Learn how to use the SMS Central provider to send SMS notifications using Novu.</Card>
<Card title="SMS Webhook" href="/platform/integrations/sms/sms-webhook"> Learn how to send SMS through your own HTTP API using Novu.</Card>
<Card title="SMS77" href="/platform/integrations/sms/sms77"> Learn how to use the SMS77 provider to send SMS notifications using Novu.</Card>
<Card title="SNS" href="/platform/integrations/sms/sns"> Learn how to use the SNS provider to send SMS notifications using Novu.</Card>
<Card title="Telnyx" href="/platform/integrations/sms/telnyx"> Learn how to use the Telnyx provider to send SMS notifications using Novu.</Card>
Expand Down
120 changes: 120 additions & 0 deletions docs/platform/integrations/sms/sms-webhook.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: "SMS Webhook SMS Integration with Novu"
sidebarTitle: "SMS Webhook"
description: "Connect SMS Webhook to Novu to send SMS through your own HTTP API or custom SMS provider endpoint."
---

SMS Webhook lets you send SMS notifications through any HTTP API you control. Instead of a built-in provider SDK, Novu `POST`s each message to your **Base URL** with the subscriber phone number, message body, and sender ID.

Use this integration when your SMS gateway exposes a REST API that is not listed in Novu's provider catalog, or when you want Novu to call an internal service that handles delivery.

## Configure SMS Webhook in Novu

<Steps>
<Step title="Prepare your SMS endpoint">
Your endpoint must accept `POST` requests and return a JSON response. For local testing, use a tool like [webhook.site](https://webhook.site/).
</Step>

<Step title="Open the Integration Store">
In the Novu dashboard, go to **Integrations Store** and click **Connect Provider**.
</Step>

<Step title="Select SMS Webhook">
Under **SMS**, select **SMS Webhook** and click **Create Integration**.
</Step>

<Step title="Fill in integration fields">
| Field | Required | Description |
| --- | --- | --- |
| **Base URL** | Yes | The URL Novu calls to send each SMS. |
| **API Key Request Header** | Yes | Header name for your API key (for example, `X-API-KEY`). |
| **API Key** | Yes | Value sent in that header. |
| **Secret Key Request Header** | No | Optional second header name. |
| **Secret Key** | No | Value for the optional secret header. |
| **From** | Yes | Default sender ID shown on outbound messages. |
| **Id Path** | Yes | Dot path to the message ID in your JSON response (default: `data.id`). |
| **Date Path** | No | Dot path to the sent date in your response (default: `data.date`). |
| **Authenticate by token** | No | Fetch a token from an auth URL before each send. |
| **Auth URL** | If token auth | URL Novu calls to obtain the token. |
| **Authentication Token Key** | If token auth | Response field and header name for the token. |
</Step>

<Step title="Activate the integration">
Mark the integration as **Active** and save.
</Step>
</Steps>

## Request format

For each SMS, Novu sends a `POST` request to your **Base URL** with the configured API key headers.

```json
{
"to": "+1234567890",
"content": "Your verification code is 123456",
"sender": "MyApp"
}
```

| Field | Description |
| --- | --- |
| `to` | Recipient phone number from the subscriber profile or trigger override. |
| `content` | Rendered SMS body from the workflow step. |
| `sender` | Sender ID from the integration **From** field or trigger override. |

## Response format

Return a `2xx` JSON response. Novu reads the message ID and date using the **Id Path** and **Date Path** you configured.

Example response when **Id Path** is `message.id` and **Date Path** is `message.date`:

```json
{
"message": {
"id": "msg_abc123",
"date": "2026-01-14T10:30:00.000Z"
}
}
```

If the date field is missing, Novu uses the current timestamp.

## Token authentication (optional)

When **Authenticate by token** is enabled:

1. Novu `POST`s to your **Auth URL** with the API key headers.
2. Novu reads the token from `data.<Authentication Token Key>` in the response.
3. Novu sends the SMS request to **Base URL** with that token in a header named after **Authentication Token Key**.

## Customize the request body

Use [provider overrides](/platform/integrations/trigger-overrides#provider-overrides) to merge extra fields into the outbound request:

```typescript
import { Novu } from '@novu/api';

const novu = new Novu({ secretKey: '<YOUR_SECRET_KEY_HERE>' });

await novu.trigger({
workflowId: 'workflow-id',
to: { subscriberId: 'user-123' },
overrides: {
providers: {
'generic-sms': {
_passthrough: {
body: {
templateId: 'otp-template',
},
},
},
},
},
});
```

The `_passthrough.body` fields are deep-merged into the default payload. See [SMS overrides](/platform/integrations/sms#override-sms-settings) to change `to`, `from`, or `content` at trigger time.

<Note>
On Novu Cloud, **Base URL** and **Auth URL** must be publicly reachable HTTPS endpoints. Private or internal URLs are blocked by SSRF protection.
</Note>
2 changes: 1 addition & 1 deletion packages/shared/src/consts/providers/channels/sms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export const smsProviders: IProviderConfig[] = [
displayName: `SMS Webhook`,
channel: ChannelTypeEnum.SMS,
credentials: genericSmsConfig,
docReference: `https://docs.novu.co/channels/sms/generic-sms${UTM_CAMPAIGN_QUERY_PARAM}`,
docReference: `https://docs.novu.co/platform/integrations/sms/sms-webhook${UTM_CAMPAIGN_QUERY_PARAM}`,
logoFileName: { light: 'generic-sms.svg', dark: 'generic-sms.svg' },
},
{
Expand Down
Loading