From 9400a63dbd50654c852723a24d2c07ac99e3c740 Mon Sep 17 00:00:00 2001 From: Pawan Jain Date: Tue, 1 Sep 2026 10:48:08 +0530 Subject: [PATCH 1/2] feat(docs): add documentation for various webhook connectors and restructure guides (#12515) --- docs/docs.json | 61 +- docs/guides.mdx | 22 +- .../webhook-connectors/amazon-eventbridge.mdx | 193 ++++ .../webhook-connectors/amazon-redshift.mdx | 193 ++++ docs/guides/webhook-connectors/amazon-s3.mdx | 110 ++ docs/guides/webhook-connectors/amazon-sns.mdx | 186 ++++ docs/guides/webhook-connectors/amazon-sqs.mdx | 178 ++++ .../webhook-connectors/azure-blob-storage.mdx | 115 +++ docs/guides/webhook-connectors/clickhouse.mdx | 165 +++ .../data-warehouse-sync.mdx | 68 ++ .../webhook-connectors/fifo-endpoint.mdx | 140 +++ .../webhook-connectors/google-bigquery.mdx | 170 ++++ .../google-cloud-pubsub.mdx | 140 +++ .../google-cloud-storage.mdx | 108 ++ .../opentelemetry-collector.mdx | 147 +++ .../webhook-connectors/polling-endpoint.mdx | 140 +++ docs/guides/webhook-connectors/postgres.mdx | 171 ++++ docs/guides/webhook-connectors/rabbitmq.mdx | 152 +++ docs/guides/webhook-connectors/segment.mdx | 142 +++ docs/guides/webhook-connectors/snowflake.mdx | 180 ++++ .../developer/webhooks/connectors.mdx | 950 ++---------------- .../developer/webhooks/event-types.mdx | 69 +- docs/platform/developer/webhooks/webhooks.mdx | 4 +- docs/platform/inbound-email/overview.mdx | 6 +- .../object-storage-transformation.mdx | 75 ++ docs/snippets/webhook-batch-input.mdx | 7 + .../webhook-connector-add-endpoint.mdx | 5 + docs/snippets/webhook-connector-related.mdx | 5 + .../snippets/webhook-connectors-plan-note.mdx | 3 + 29 files changed, 3011 insertions(+), 894 deletions(-) create mode 100644 docs/guides/webhook-connectors/amazon-eventbridge.mdx create mode 100644 docs/guides/webhook-connectors/amazon-redshift.mdx create mode 100644 docs/guides/webhook-connectors/amazon-s3.mdx create mode 100644 docs/guides/webhook-connectors/amazon-sns.mdx create mode 100644 docs/guides/webhook-connectors/amazon-sqs.mdx create mode 100644 docs/guides/webhook-connectors/azure-blob-storage.mdx create mode 100644 docs/guides/webhook-connectors/clickhouse.mdx create mode 100644 docs/guides/webhook-connectors/data-warehouse-sync.mdx create mode 100644 docs/guides/webhook-connectors/fifo-endpoint.mdx create mode 100644 docs/guides/webhook-connectors/google-bigquery.mdx create mode 100644 docs/guides/webhook-connectors/google-cloud-pubsub.mdx create mode 100644 docs/guides/webhook-connectors/google-cloud-storage.mdx create mode 100644 docs/guides/webhook-connectors/opentelemetry-collector.mdx create mode 100644 docs/guides/webhook-connectors/polling-endpoint.mdx create mode 100644 docs/guides/webhook-connectors/postgres.mdx create mode 100644 docs/guides/webhook-connectors/rabbitmq.mdx create mode 100644 docs/guides/webhook-connectors/segment.mdx create mode 100644 docs/guides/webhook-connectors/snowflake.mdx create mode 100644 docs/snippets/object-storage-transformation.mdx create mode 100644 docs/snippets/webhook-batch-input.mdx create mode 100644 docs/snippets/webhook-connector-add-endpoint.mdx create mode 100644 docs/snippets/webhook-connector-related.mdx create mode 100644 docs/snippets/webhook-connectors-plan-note.mdx diff --git a/docs/docs.json b/docs/docs.json index 17e5be79059..516eec4b3bb 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -918,14 +918,69 @@ "pages": ["guides/inngest", "guides/triggerdotdev"] }, { - "group": "Webhooks", + "group": "Incoming webhooks", "expanded": false, "pages": ["guides/webhooks/clerk", "guides/webhooks/auth0", "guides/webhooks/stripe"] }, { - "group": "Data & warehouses", + "group": "Webhook connectors", "expanded": false, - "pages": ["guides/analytics/segment", "guides/analytics/hightouch", "guides/analytics/databricks"] + "pages": [ + { + "group": "Data warehouses", + "expanded": false, + "pages": [ + "guides/webhook-connectors/data-warehouse-sync", + "guides/webhook-connectors/clickhouse", + "guides/webhook-connectors/google-bigquery", + "guides/webhook-connectors/snowflake", + "guides/webhook-connectors/amazon-redshift", + "guides/webhook-connectors/postgres" + ] + }, + { + "group": "Object storage", + "expanded": false, + "pages": [ + "guides/webhook-connectors/amazon-s3", + "guides/webhook-connectors/azure-blob-storage", + "guides/webhook-connectors/google-cloud-storage" + ] + }, + { + "group": "Messaging", + "expanded": false, + "pages": [ + "guides/webhook-connectors/amazon-sqs", + "guides/webhook-connectors/amazon-sns", + "guides/webhook-connectors/amazon-eventbridge", + "guides/webhook-connectors/rabbitmq", + "guides/webhook-connectors/google-cloud-pubsub" + ] + }, + { + "group": "Analytics & observability", + "expanded": false, + "pages": [ + "guides/webhook-connectors/segment", + "guides/webhook-connectors/opentelemetry-collector" + ] + }, + { + "group": "HTTP delivery", + "expanded": false, + "pages": ["guides/webhook-connectors/polling-endpoint", "guides/webhook-connectors/fifo-endpoint"] + } + ] + }, + { + "group": "Data sources", + "expanded": false, + "pages": [ + "guides/analytics/segment", + "guides/analytics/hightouch", + "guides/analytics/databricks" + ] }, { "group": "Recipes", diff --git a/docs/guides.mdx b/docs/guides.mdx index afce858b0a2..fb4bd9ed78d 100644 --- a/docs/guides.mdx +++ b/docs/guides.mdx @@ -4,6 +4,7 @@ description: "Step-by-step Novu integration guides for Clerk, Stripe, Segment, H sidebarTitle: Overview --- +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; ## Integration Guides @@ -26,7 +27,7 @@ Novu provides various ways to integrate with external services to trigger notifi -### Webhook Integration Guides +### Incoming webhook guides Webhooks enable real-time event-driven communication between applications, making integrations more efficient and responsive. @@ -54,9 +55,24 @@ This allows for real-time notifications - whether it's a welcome email, payment -### Data & warehouse integrations +### Webhook connector guides -These guides sync warehouse or CDP data into Novu subscribers and topics. They do not replace event-stream triggers for real-time notifications. +Webhook connectors send Novu delivery and engagement events to data warehouses, object storage, queues, analytics tools, and HTTP consumers. + + + + + + Compare every supported destination and open its step-by-step setup guide. + + + Plan a notification analytics export, then configure BigQuery, ClickHouse, Snowflake, Redshift, or Postgres. + + + +### Data sources + +Sync warehouse and CDP data into Novu to create subscribers and trigger workflows. diff --git a/docs/guides/webhook-connectors/amazon-eventbridge.mdx b/docs/guides/webhook-connectors/amazon-eventbridge.mdx new file mode 100644 index 00000000000..73caffa1bc3 --- /dev/null +++ b/docs/guides/webhook-connectors/amazon-eventbridge.mdx @@ -0,0 +1,193 @@ +--- +title: 'Send webhook events to Amazon EventBridge' +description: 'Configure the Amazon EventBridge webhook connector to publish Novu events to an event bus, including the batch transformation contract, detail type behavior, and rule matching.' +sidebarTitle: 'Amazon EventBridge' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Amazon EventBridge connector publishes selected Novu webhook events to an EventBridge event bus. Novu delivers events in batches, and every event in a batch is sent to the bus as a separate entry. Use this connector to route Novu events with EventBridge rules to targets such as Lambda functions, Step Functions state machines, or queues, without hosting an HTTP receiver. + + + +## Prerequisites + +- An existing event bus in the AWS account and region you want to publish to, or the default bus +- An AWS access key ID and secret access key for a principal that can put events on that bus +- Permission to manage webhook endpoints in the Novu environment you are configuring +- A rule and target on the bus, or CloudWatch Logs as a target, so you can observe delivered events + +### Required AWS permission + +Batches are delivered with the `PutEvents` API, which is authorized by the `events:PutEvents` action on the destination bus. Additional permissions may be required by your own setup, for example KMS key permissions when the bus uses encryption with a customer managed key, or permissions needed to satisfy an organizational policy, service control policy, or resource policy condition. + +Novu configures this connector through the embedded webhook portal. The portal collects the bus name and credentials and does not create the bus, create rules, or evaluate an IAM policy document for you. + +## Configuration + +These are the fields collected for the Amazon EventBridge connector. + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Event bus name** | Yes | Name or ARN of the event bus that receives the events. | +| **Detail type** | No | Free-form string with a maximum of 128 characters, used as the `detail-type` of each event. Defaults to `application/json`. | +| **Region** | Yes | AWS region that hosts the event bus. | +| **Access key ID** | Yes | AWS access key ID used to authenticate. | +| **Secret access key** | Yes | AWS secret access key paired with the access key ID. | +| **Transformation** | Yes | JavaScript that shapes the `detail` body of each event. | + +There is no webhook URL field for this connector, and no custom endpoint field. Delivery targets the bus through the AWS EventBridge API. + +## Event envelope + +Each published event uses the following envelope: + +| Envelope field | Value | +| --- | --- | +| `source` | Set automatically by the delivery system and not configurable in the transformation. The documented form is `svix-webhooks-`, where the application id is internal to your Novu environment. | +| `detail-type` | The configured **Detail type**, or `application/json` when left empty. | +| `detail` | One string from the transformation's `payloads` array. | + +Write EventBridge rules that match on `source` and `detail-type`. Because the `source` value contains an internal application id, send a test event first and read the exact `source` from the received event before you pin a rule to it. If you want a stable, human-readable discriminator, set **Detail type** and match on that. + +## Transformation contract + +The handler receives a batch and returns the event details to publish. + + + +The handler must return: + +```ts +{ + payloads: string[]; +} +``` + +Each string in `payloads` becomes the `detail` of one EventBridge event. The default template serializes each event to a JSON string: + +```js +function handler(input) { + const payloads = input.events.map((event) => JSON.stringify(event)); + + return { + payloads, + }; +} +``` + +With the default template, two events produce two `detail` bodies: + +```json +{"payload":{"email":"joe@enterprise.io"},"eventType":"user.created"} +``` + +```json +{"payload":{"id":12,"timestamp":"2025-07-21T14:23:17.861Z"},"eventType":"user.login"} +``` + +### Novu example + +This example keeps the Novu event type at the top level of `detail`, so rules can match on it with a content filter even though `detail-type` is a single configured value for the endpoint. + +```js +function handler(input) { + const payloads = input.events.map((event) => + JSON.stringify({ + eventType: event.eventType, + payload: event.payload, + }) + ); + + return { + payloads, + }; +} +``` + +Entries in `payloads` must be strings. Returning objects instead of strings does not match the contract, so serialize with `JSON.stringify` before returning. The number of strings you return controls how many events are put on the bus, so you can filter a batch by returning fewer entries. + +### Batching and limits + +EventBridge accepts at most 10 entries per request, so larger batches are automatically split across multiple `PutEvents` calls. Do not assume a specific endpoint batch size or wait interval. Design rules and targets to handle each event independently, because entries from the same Novu batch can be delivered in separate requests. + +## Configure in the Dashboard + + + +## Prepare the event bus + +Create the bus or choose the default bus, and add a rule with an observable target so you can inspect delivered events. A CloudWatch Logs target is convenient for the first test. + + + +## Create credentials + +Create an access key for a principal that can perform `events:PutEvents` on the bus. If the bus is encrypted with a customer managed key, confirm the credentials can use that key. + + + + + +## Enter connection details + +Enter **Event bus name**, **Region**, **Access key ID**, and **Secret access key**. Set **Detail type** if you want a stable value to match in rules, and keep it within 128 characters. + + + +## Configure the transformation + +Start from the provided template and send a test before customizing. Keep every entry in `payloads` a serialized string, and include any field your rules need to filter on inside `detail`. + + + +## Select event types + +Select the [event types](/platform/developer/webhooks/event-types) you want to publish. Only selected event types reach the endpoint and its transformation, so start with one event type when validating a new bus. + + + +## Test the endpoint + +Create the endpoint, open **Testing**, and send an example for a subscribed event type. Confirm the attempt succeeded in **Logs**, then inspect the rule target and compare the received `detail` with the logged transformation output. + + + +## Finalize the rule pattern + +Using the received event, record the exact `source` and `detail-type` values and narrow your rule pattern to them. Add content filters on `detail` fields if one endpoint carries several Novu event types. + + + +## Verify delivery + +Check the following in AWS after a successful test: + +- The rule target received the event, for example a new CloudWatch Logs entry appeared. +- `detail-type` matches your configured value, or `application/json` when you left it empty. +- `detail` parses as JSON and contains the fields your targets expect. +- Event counts grow per returned `payloads` entry rather than per delivered batch. + +An event bus with no matching rule accepts the event and delivers it nowhere, so always verify through a rule target. + +## Troubleshooting + +- **Access denied on delivery**: The credentials cannot perform `events:PutEvents` on this bus. Check the identity policy, the bus resource policy, and any condition that restricts the caller. +- **The bus is not found**: Confirm the bus name or ARN is exact, and that the configured region hosts that bus. A bus in another region will not receive the events. +- **Delivery succeeds but no target runs**: No rule matched. Compare your rule pattern with the actual `source` and `detail-type` on a received event, and remember `source` contains an internal application id. +- **All events look the same to rules**: `detail-type` is a single configured value per endpoint. Add a discriminator such as the event type inside `detail` and match with a content filter. +- **Detail type is rejected**: Keep the value within the 128 character maximum. +- **Targets cannot parse the detail**: Confirm each `payloads` entry is a serialized JSON string rather than an object. +- **Some event types never arrive**: Review the endpoint's selected event types, and test each event type separately from the **Testing** tab. + + + + +## Official references + +- [Svix Amazon EventBridge endpoints](https://docs.svix.com/advanced-endpoints/eventbridge) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Amazon EventBridge documentation](https://docs.aws.amazon.com/eventbridge/) diff --git a/docs/guides/webhook-connectors/amazon-redshift.mdx b/docs/guides/webhook-connectors/amazon-redshift.mdx new file mode 100644 index 00000000000..2b03700b75a --- /dev/null +++ b/docs/guides/webhook-connectors/amazon-redshift.mdx @@ -0,0 +1,193 @@ +--- +title: 'Load webhook events into Amazon Redshift' +description: "Write Novu webhook events to an Amazon Redshift table with the Redshift connector, covering Serverless and provisioned clusters, the Redshift Data API, and named SQL bindings." +sidebarTitle: Amazon Redshift +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Amazon Redshift connector inserts Novu webhook events into a Redshift table through the [Redshift Data API](https://docs.aws.amazon.com/redshift-data/latest/APIReference/Welcome.html). Both Redshift Serverless and provisioned clusters are supported. Each batch of webhooks becomes one parameterized SQL statement. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [Redshift advanced endpoint](https://docs.svix.com/advanced-endpoints/redshift). + + + +## Prerequisites + +- A Redshift Serverless workgroup, or a provisioned Redshift cluster and the database user to connect as. +- IAM credentials, an access key ID and secret access key, allowed to call the Redshift Data API. +- The destination table created before you enable the endpoint. The connector does not create or alter tables. + +Because delivery goes through the Data API rather than a direct database connection, you do not need to open a database port to Novu. + +## Configuration + +| Field | Required | Description | +| --- | --- | --- | +| Region | Yes | AWS region of the workgroup or cluster. | +| Access key ID | Yes | AWS access key ID used to authenticate. | +| Secret access key | Yes | AWS secret access key used to authenticate. | +| Workgroup name | For Redshift Serverless | Name of the Serverless workgroup. | +| Cluster identifier | For provisioned clusters | Identifier of the provisioned cluster. | +| Database user | For provisioned clusters | Database user to connect as. | +| Database name | Only without a transformation | Database to write to. | +| Schema name | No | Schema that contains the table. Used only without a transformation. | +| Table name | Only without a transformation | Table that receives the rows. | +| Transformation | No | JavaScript that returns the SQL statement and bindings to run. | + +Set the Serverless fields or the provisioned cluster fields, not both. When you set a transformation, the statement names the target table directly, so database, schema, and table become optional. + +## IAM permissions + +Svix does not publish the exact Data API actions this connector calls, so start from AWS's own reference for [authorizing access to the Redshift Data API](https://docs.aws.amazon.com/redshift/latest/mgmt/data-api-access.html). AWS documents that callers need `redshift-data` permissions to run statements and read their status, plus credential permissions that depend on the deployment type: `redshift-serverless:GetCredentials` for Serverless, or `redshift:GetClusterCredentials` for a provisioned cluster with a database user. + +Start narrow, then widen only if a delivery fails with an authorization error visible in the endpoint **Logs** tab. + +Grant insert access inside the database separately with Redshift [`GRANT`](https://docs.aws.amazon.com/redshift/latest/dg/r_GRANT.html): + +```sql +GRANT INSERT ON TABLE novu_events TO awsuser; +``` + +## Default destination behavior + +Without a transformation, Svix inserts into the table identified by the database, schema, and table fields using two columns. It sets `created_at` to the insert time and writes the raw payload to `payload`. Note that unlike the BigQuery and Snowflake connectors, no `id` column is generated: + +```sql +CREATE TABLE events ( + created_at TIMESTAMP, + payload VARCHAR(65535) +); +``` + +`VARCHAR(65535)` is the largest `VARCHAR` Redshift allows. If a webhook payload is larger than the column can hold, it cannot be written, and the endpoint is disabled. Keep this in mind if you subscribe to event types with large payloads. + +The table must exist before you enable the endpoint. + + +## Create an analytics table + +```sql +CREATE TABLE novu_events ( + received_at TIMESTAMP, + event_type VARCHAR(256), + subscriber_id VARCHAR(256), + channel VARCHAR(64), + payload VARCHAR(65535) +); +``` + +## Transformation contract + +The transformation builds one parameterized statement per batch: + + +- Output: an object with `statement` and `bindings`. +- `bindings` is row-oriented. It is an array of `{ name, value }` objects, and the statement references each one by name, for example `:payload0`. This differs from the Snowflake connector, which uses column-oriented bindings. +- The statement runs against your database through the Redshift Data API. + +Novu-oriented example that matches the analytics table above and reads message fields from `event.payload.data.object`: + +```javascript +function handler(input) { + const bindings = []; + const values = []; + + input.events.forEach((event, index) => { + const message = event.payload.data?.object ?? {}; + + bindings.push( + { name: `event_type${index}`, value: String(event.eventType) }, + { name: `subscriber_id${index}`, value: String(message.subscriberId ?? "") }, + { name: `channel${index}`, value: String(message.channel ?? "") }, + { name: `payload${index}`, value: JSON.stringify(event.payload) } + ); + + values.push( + `(CURRENT_TIMESTAMP, :event_type${index}, :subscriber_id${index}, :channel${index}, :payload${index})` + ); + }); + + return { + bindings, + statement: `INSERT INTO novu_events (received_at, event_type, subscriber_id, channel, payload) VALUES ${values.join(", ")};`, + }; +} +``` + +Suffix binding names with the row index, as above. Reusing a name across rows collapses the values. + +## Configure in the Dashboard + + + + + + +## Enter connection details + +Enter the region and IAM credentials. For Serverless, set the workgroup name. For a provisioned cluster, set the cluster identifier and database user. Fill in database, schema, and table if you are relying on the default insert behavior. + + + +## Configure the transformation + +Paste a transformation whose statement names your table and whose bindings match its columns. + + + +## Select event types + +Subscribe to the [event types](/platform/developer/webhooks/event-types) you want to store. Every subscribed type runs through the same statement, so confirm the bindings resolve for all of them. + + + +## Test the endpoint + +Click **Create**, open the endpoint, go to the **Testing** tab, and use **Send Example** for each subscribed event type. Confirm success in the **Logs** tab. + + + + +## Verify delivery + +Query through the same Data API the connector uses. Submit the statement: + +```bash +aws redshift-data execute-statement \ + --region us-east-1 \ + --workgroup-name my-workgroup \ + --database dev \ + --sql "SELECT received_at, event_type, subscriber_id, channel FROM novu_events ORDER BY received_at DESC LIMIT 10" +``` + +Then read the result with the returned statement ID: + +```bash +aws redshift-data get-statement-result --region us-east-1 --id +``` + +For a provisioned cluster, replace `--workgroup-name` with `--cluster-identifier` and `--db-user`. + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| Endpoint was disabled after a delivery | A payload exceeded what the `payload` column can store. `VARCHAR(65535)` is the Redshift maximum, so trim the payload in the transformation or store selected fields instead of the whole body. | +| Authorization or access denied errors | The IAM credentials are missing Data API permissions, or the credential permission for your deployment type. Check the AWS reference linked above. | +| Connection or resource not found | Serverless and provisioned fields are mixed. Set the workgroup name, or the cluster identifier with a database user, not both. | +| Permission denied for relation | The database user lacks `INSERT` on the table. | +| Statement fails to parse | Inspect the generated SQL and confirm every named binding appears in the statement. | +| Values land in the wrong rows | Binding names are reused across rows. Suffix each name with the row index. | +| Table not found | Database, schema, and table fields are only used without a transformation. With a transformation, the name in the statement is what matters, including the schema if the table is not on the search path. | + + + +## Official references + +- [Svix Amazon Redshift endpoint](https://docs.svix.com/advanced-endpoints/redshift) +- [Authorizing access to the Redshift Data API](https://docs.aws.amazon.com/redshift/latest/mgmt/data-api-access.html) +- [Amazon Redshift Data API](https://docs.aws.amazon.com/redshift-data/latest/APIReference/Welcome.html) diff --git a/docs/guides/webhook-connectors/amazon-s3.mdx b/docs/guides/webhook-connectors/amazon-s3.mdx new file mode 100644 index 00000000000..f48cb6e8576 --- /dev/null +++ b/docs/guides/webhook-connectors/amazon-s3.mdx @@ -0,0 +1,110 @@ +--- +title: 'Store webhook events in Amazon S3' +description: 'Configure the Amazon S3 webhook connector to archive batched Novu events as objects, including the transformation contract, object key behavior, and verification steps.' +sidebarTitle: 'Amazon S3' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import ObjectStorageTransformation from "/snippets/object-storage-transformation.mdx"; + +The Amazon S3 connector writes selected Novu webhook events to an S3 bucket. Novu delivers events in batches, and each delivered batch creates one new object in the bucket. Use this connector to build a durable event archive for replay, auditing, or downstream processing with tools that read from S3. + + + +## Prerequisites + +- An existing S3 bucket in the AWS account and region you want to write to +- An AWS access key ID and secret access key for a principal that can write objects to that bucket +- Permission to manage webhook endpoints in the Novu environment you are configuring + +### Required AWS permission + +The connector writes each batch as an object, so the credentials need the `s3:PutObject` action on the destination bucket. Additional permissions may be required by your own setup, for example KMS key permissions when the bucket enforces server-side encryption with a customer managed key, or permissions needed to satisfy an organizational policy, service control policy, or bucket policy condition. + +Novu configures this connector through the embedded webhook portal. The portal collects bucket and credential values and does not create the bucket, apply a bucket policy, or validate an IAM policy document for you. + +## Configuration + +These are the fields collected for the Amazon S3 connector. + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Bucket** | Yes | Name of the destination S3 bucket. | +| **Region** | Yes | AWS region that hosts the bucket. | +| **Access key ID** | Yes | AWS access key ID used to authenticate. | +| **Secret access key** | Yes | AWS secret access key paired with the access key ID. | +| **Transformation** | Yes | JavaScript that sets the object key, the object format, and the object contents. | + +There is no URL field for this connector. Delivery targets the bucket through the AWS S3 API rather than an HTTPS endpoint you host. + + + +## Configure in the Dashboard + + + +## Prepare the bucket + +Create the destination bucket in the region you plan to configure, and decide on the key prefix you want, for example a date partition. If the bucket enforces encryption with a customer managed key, confirm the credentials can use that key. + + + +## Create credentials + +Create an access key for a principal that can perform `s3:PutObject` on the bucket. Store the secret access key securely, because it is entered once into the connector configuration. + + + + + +## Enter connection details + +Enter **Bucket**, **Region**, **Access key ID**, and **Secret access key**. Add a description that identifies the destination, for example the bucket name and the AWS account it belongs to. + + + +## Configure the transformation + +Start from the provided template. Change `config.key` to the prefix you want and keep `config.format` aligned with the type of `data` you return. + + + +## Select event types + +Select the [event types](/platform/developer/webhooks/event-types) you want to archive. Only selected event types reach the endpoint and its transformation, so start with one event type when validating a new bucket. + + + +## Test the endpoint + +Create the endpoint, open **Testing**, and send an example for a subscribed event type. Confirm the attempt succeeded in **Logs**, then list the bucket under your key prefix and confirm a new timestamp-suffixed object exists. Download the object and confirm its contents match the format you configured. + + + +## Verify delivery + +Check the following in AWS after a successful test: + +- The object exists under the prefix you set in `config.key`, with a timestamp appended. +- The object contents parse in the format you selected, one JSON document per line for `"jsonl"`. +- The object count grows by one per delivered batch rather than one per event. + +## Troubleshooting + +- **Access denied on delivery**: The credentials cannot perform `s3:PutObject` on this bucket. Check the bucket policy, the identity policy, and any condition that restricts the caller, prefix, or encryption headers. +- **The bucket or region is rejected**: Confirm the bucket name is exact and the region matches the bucket's actual region. A bucket in another region will not accept the request. +- **Delivery succeeds but no object appears**: You are likely looking at the wrong prefix. Compare the transformed output in **Logs** with the `config.key` value, then list the bucket at that prefix. +- **Object contents are not what you expect**: Confirm `config.format` matches the type of `data`. For `"raw"`, `data` must be a string containing the exact contents. +- **Encrypted buckets fail**: If the bucket requires a customer managed KMS key, the credentials also need permission to use that key. +- **Some event types never appear**: Review the endpoint's selected event types, and test each event type separately from the **Testing** tab. + + + + +## Official references + +- [Svix object storage endpoints](https://docs.svix.com/advanced-endpoints/object-storage) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Amazon S3 documentation](https://docs.aws.amazon.com/s3/) diff --git a/docs/guides/webhook-connectors/amazon-sns.mdx b/docs/guides/webhook-connectors/amazon-sns.mdx new file mode 100644 index 00000000000..f7fc22be9bd --- /dev/null +++ b/docs/guides/webhook-connectors/amazon-sns.mdx @@ -0,0 +1,186 @@ +--- +title: 'Publish webhook events to Amazon SNS' +description: 'Configure the Amazon SNS webhook connector to publish Novu events to a topic, including the batch transformation contract, message subject support, and verification steps.' +sidebarTitle: 'Amazon SNS' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Amazon SNS connector publishes selected Novu webhook events to an SNS topic. Novu delivers events in batches, and every event in a batch is published to the topic as a separate message. Use this connector to fan a single Novu event out to multiple subscribers, such as an SQS queue, a Lambda function, and an HTTP subscriber at the same time. + + + +## Prerequisites + +- An existing SNS topic in the AWS account and region you want to publish to +- The topic ARN, for example `arn:aws:sns:us-east-1:000000000000:my-topic` +- An AWS access key ID and secret access key for a principal that can publish to that topic +- At least one topic subscription you can inspect during verification +- Permission to manage webhook endpoints in the Novu environment you are configuring + +### Required AWS permission + +Batches are delivered with the `PublishBatch` API, which is authorized by the `sns:Publish` action on the destination topic. Additional permissions may be required by your own setup, for example KMS key permissions when the topic uses server-side encryption with a customer managed key, or permissions needed to satisfy an organizational policy, service control policy, or topic policy condition. + +Novu configures this connector through the embedded webhook portal. The portal collects the topic ARN and credentials and does not create the topic, add subscriptions, or evaluate an IAM policy document for you. + +## Configuration + +These are the fields collected for the Amazon SNS connector. + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Topic ARN** | Yes | ARN of the destination SNS topic. | +| **Region** | Yes | AWS region that hosts the topic. | +| **Access key ID** | Yes | AWS access key ID used to authenticate. | +| **Secret access key** | Yes | AWS secret access key paired with the access key ID. | +| **Endpoint URL** | No | Optional custom endpoint for SNS-compatible services, for example a local development stack. Leave it empty for standard AWS SNS. | +| **Transformation** | Yes | JavaScript that shapes the published message body and optional subject. | + +There is no webhook URL field for this connector. Delivery targets the topic through the AWS SNS API rather than an HTTPS endpoint you host. + +## Transformation contract + +The handler receives a batch and returns the messages to publish. + + + +The handler must return: + +```ts +{ + messages: Array<{ + payload: unknown; + subject?: string; + }>; +} +``` + +| Return field | Behavior | +| --- | --- | +| `messages[].payload` | Becomes the SNS message body. | +| `messages[].subject` | Optional SNS subject for that message. Omitted by default. | + +The default template publishes one message per event, serialized as JSON, with no subject: + +```js +function handler(input) { + const messages = input.events.map((event) => ({ + payload: event, + })); + + return { + messages, + }; +} +``` + +With the default template, two events produce two message bodies: + +```json +{"payload":{"email":"joe@enterprise.io"},"eventType":"user.created"} +``` + +```json +{"payload":{"id":12,"timestamp":"2025-07-21T14:23:17.861Z"},"eventType":"user.login"} +``` + +### Novu example + +This example sets the Novu event type as the subject, which is useful when subscribers filter or display the subject, and keeps the full event in the body. + +```js +function handler(input) { + const messages = input.events.map((event) => ({ + payload: JSON.stringify({ + eventType: event.eventType, + payload: event.payload, + }), + subject: event.eventType, + })); + + return { + messages, + }; +} +``` + +Subject values are visible to subscribers such as email, so avoid putting subscriber data or other sensitive values in `subject`. Keep it to the event type or another low cardinality label. + +### Batching and limits + +SNS accepts at most 10 messages per batch request, so larger batches are automatically split across multiple `PublishBatch` calls. Do not assume a specific endpoint batch size or wait interval. Design subscribers to handle each message independently, because messages from the same Novu batch can be published in separate requests. + +## Configure in the Dashboard + + + +## Prepare the topic + +Create the destination topic, copy its ARN, and attach at least one subscription you can inspect, such as an SQS queue you control. Confirm the topic's region matches the region you will configure. + + + +## Create credentials + +Create an access key for a principal that can perform `sns:Publish` on the topic. If the topic is encrypted with a customer managed key, confirm the credentials can use that key. + + + + + +## Enter connection details + +Enter **Topic ARN**, **Region**, **Access key ID**, and **Secret access key**. Leave **Endpoint URL** empty unless you are targeting an SNS-compatible service. Add a description that identifies the topic and AWS account. + + + +## Configure the transformation + +Start from the provided template and send a test before customizing. Add `subject` only if your subscribers use it, and keep the body shape stable for existing subscribers. + + + +## Select event types + +Select the [event types](/platform/developer/webhooks/event-types) you want to publish. Only selected event types reach the endpoint and its transformation, so start with one event type when validating a new topic. + + + +## Test the endpoint + +Create the endpoint, open **Testing**, and send an example for a subscribed event type. Confirm the attempt succeeded in **Logs**, then inspect a subscription and compare the delivered message with the logged transformation output. + + + +## Verify delivery + +Check the following in AWS after a successful test: + +- A subscriber received the message, for example the subscribed SQS queue has an available message. +- The message body matches the shape your transformation returns, and the subject matches if you set one. +- Delivery counts grow per returned message rather than per delivered batch. + +A topic without subscriptions accepts publishes and discards them, so always verify through a subscription rather than the topic alone. + +## Troubleshooting + +- **Access denied on delivery**: The credentials cannot perform `sns:Publish` on this topic. Check the identity policy, the topic policy, and any condition that restricts the caller. +- **The topic ARN is rejected**: Confirm the ARN is exact, including account ID and topic name, and that the configured region matches the region inside the ARN. +- **Delivery succeeds but nothing is received**: The topic likely has no matching subscription, or a subscription filter policy excluded the message. Check the subscription list and its filter policy. +- **Encrypted topics fail**: If the topic uses a customer managed KMS key, the credentials also need permission to use that key. +- **Subscribers cannot parse the body**: Confirm what your transformation returns in `payload`. Returning a string publishes that string as the body, while returning an object publishes its serialized JSON. +- **The subject is missing**: `subject` is optional and omitted by the default template. Set it explicitly if subscribers depend on it. +- **Some event types never arrive**: Review the endpoint's selected event types, and test each event type separately from the **Testing** tab. + + + + +## Official references + +- [Svix Amazon SNS endpoints](https://docs.svix.com/advanced-endpoints/sns) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Amazon SNS documentation](https://docs.aws.amazon.com/sns/) diff --git a/docs/guides/webhook-connectors/amazon-sqs.mdx b/docs/guides/webhook-connectors/amazon-sqs.mdx new file mode 100644 index 00000000000..52ce0804478 --- /dev/null +++ b/docs/guides/webhook-connectors/amazon-sqs.mdx @@ -0,0 +1,178 @@ +--- +title: 'Send webhook events to Amazon SQS' +description: 'Configure the Amazon SQS webhook connector to deliver Novu events as queue messages, including the batch transformation contract, message body control, and verification steps.' +sidebarTitle: 'Amazon SQS' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Amazon SQS connector sends selected Novu webhook events to an SQS queue. Novu delivers events in batches, and every event in a batch is sent to the queue as a separate message. Use this connector when you want to process Novu events asynchronously with your own consumers instead of exposing an HTTP receiver. + + + +## Prerequisites + +- An existing SQS queue in the AWS account and region you want to send to +- The full queue URL, for example `https://sqs.us-east-1.amazonaws.com/000000000000/my-queue` +- An AWS access key ID and secret access key for a principal that can send messages to that queue +- Permission to manage webhook endpoints in the Novu environment you are configuring + +### Required AWS permission + +Batches are delivered with the `SendMessageBatch` API, which is authorized by the `sqs:SendMessage` action on the destination queue. Additional permissions may be required by your own setup, for example KMS key permissions when the queue uses server-side encryption with a customer managed key, or permissions needed to satisfy an organizational policy, service control policy, or queue policy condition. + +Novu configures this connector through the embedded webhook portal. The portal collects the queue URL and credentials and does not create the queue or evaluate an IAM policy document for you. + +## Configuration + +These are the fields collected for the Amazon SQS connector. + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Queue URL** | Yes | Full URL of the destination SQS queue. | +| **Region** | Yes | AWS region that hosts the queue. | +| **Access key ID** | Yes | AWS access key ID used to authenticate. | +| **Secret access key** | Yes | AWS secret access key paired with the access key ID. | +| **Endpoint URL** | No | Optional custom endpoint for SQS-compatible services, for example a local development stack. Leave it empty for standard AWS SQS. | +| **Transformation** | Yes | JavaScript that shapes the body of each queue message. | + +There is no webhook URL field for this connector. Delivery targets the queue through the AWS SQS API rather than an HTTPS endpoint you host. + +## Transformation contract + +The handler receives a batch and returns the messages to send. + + + +The handler must return: + +```ts +{ + messages: Array<{ + payload: unknown; + }>; +} +``` + +Each entry in `messages` is sent as a separate SQS message, and its `payload` becomes that message's body. The default template sends one message per event, serialized as JSON: + +```js +function handler(input) { + const messages = input.events.map((event) => ({ + payload: event, + })); + + return { + messages, + }; +} +``` + +With the default template, two events produce two message bodies: + +```json +{"payload":{"email":"joe@enterprise.io"},"eventType":"user.created"} +``` + +```json +{"payload":{"id":12,"timestamp":"2025-07-21T14:23:17.861Z"},"eventType":"user.login"} +``` + +### Novu example + +This example keeps the event type at the top level of the body so consumers can route on it without parsing nested fields, and passes the Novu payload through unchanged. + +```js +function handler(input) { + const messages = input.events.map((event) => ({ + payload: JSON.stringify({ + eventType: event.eventType, + payload: event.payload, + }), + })); + + return { + messages, + }; +} +``` + +Return fewer entries than the input length if you want to filter events in the transformation, and return a single entry to combine a batch into one aggregate message. The number of messages you return controls how many queue messages are produced, independent of how many events arrived in the batch. + +### Batching and limits + +SQS accepts at most 10 messages per batch request, so larger batches are automatically split across multiple `SendMessageBatch` calls. Do not assume a specific endpoint batch size or wait interval. Design consumers to handle each message independently and to tolerate messages from the same Novu batch arriving in separate requests. + +## Configure in the Dashboard + + + +## Prepare the queue + +Create the destination queue and copy its full queue URL from the AWS console. Confirm the queue's region matches the region you will configure. + + + +## Create credentials + +Create an access key for a principal that can perform `sqs:SendMessage` on the queue. If the queue is encrypted with a customer managed key, confirm the credentials can use that key. + + + + + +## Enter connection details + +Enter **Queue URL**, **Region**, **Access key ID**, and **Secret access key**. Leave **Endpoint URL** empty unless you are targeting an SQS-compatible service. Add a description that identifies the queue and AWS account. + + + +## Configure the transformation + +Start from the provided template and send a test before customizing. When you change the body, keep the shape stable for existing consumers and version it in the payload if consumers depend on the structure. + + + +## Select event types + +Select the [event types](/platform/developer/webhooks/event-types) you want to queue. Only selected event types reach the endpoint and its transformation, so start with one event type when validating a new queue. + + + +## Test the endpoint + +Create the endpoint, open **Testing**, and send an example for a subscribed event type. Confirm the attempt succeeded in **Logs**, then read the message from the queue and compare its body with the logged transformation output. + + + +## Verify delivery + +Check the following in AWS after a successful test: + +- The queue shows available messages, or your consumer receives them. +- Each message body matches the shape your transformation returns. +- Message count grows per returned message rather than per delivered batch. + +If a consumer is already draining the queue, pause it or use a separate test queue so the test message is still visible when you look for it. + +## Troubleshooting + +- **Access denied on delivery**: The credentials cannot perform `sqs:SendMessage` on this queue. Check the identity policy, the queue policy, and any condition that restricts the caller. +- **The queue does not exist or is rejected**: Confirm the full queue URL is exact, including account ID and queue name, and that the configured region matches the queue's region. +- **Delivery succeeds but the queue looks empty**: Another consumer likely received the messages already, or messages are in flight. Check the in flight metric, or test against a queue with no active consumers. +- **Encrypted queues fail**: If the queue uses a customer managed KMS key, the credentials also need permission to use that key. +- **Message bodies are not valid JSON for consumers**: Confirm what your transformation returns in `payload`. Returning a string sends that string as the body, while returning an object sends its serialized JSON. +- **Fewer messages than events**: Confirm the transformation returns one entry per event and does not filter or combine events unintentionally. +- **Some event types never arrive**: Review the endpoint's selected event types, and test each event type separately from the **Testing** tab. + + + + +## Official references + +- [Svix Amazon SQS endpoints](https://docs.svix.com/advanced-endpoints/sqs) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Amazon SQS documentation](https://docs.aws.amazon.com/sqs/) diff --git a/docs/guides/webhook-connectors/azure-blob-storage.mdx b/docs/guides/webhook-connectors/azure-blob-storage.mdx new file mode 100644 index 00000000000..a262ee7a216 --- /dev/null +++ b/docs/guides/webhook-connectors/azure-blob-storage.mdx @@ -0,0 +1,115 @@ +--- +title: 'Send webhook events to Azure Blob Storage' +description: 'Configure the Azure Blob Storage webhook connector to store selected Novu events as batched blobs.' +sidebarTitle: 'Azure Blob Storage' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import ObjectStorageTransformation from "/snippets/object-storage-transformation.mdx"; + +The Azure Blob Storage connector writes selected Novu webhook events to a container. Each delivered batch creates a new blob. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [object storage advanced endpoint](https://docs.svix.com/advanced-endpoints/object-storage), which is shared with Amazon S3 and Google Cloud Storage. + + + +## Prerequisites + +- An Azure storage account +- An existing blob container, created before you configure the endpoint +- The storage account name and one of its access keys + +### Access model + +The connector authenticates with a storage account access key. An account key grants full access to every container in that storage account, and it cannot be narrowed to a single container the way an Azure RBAC role assignment or a container-scoped SAS token can. + +Because of that, use a storage account dedicated to webhook exports rather than one that also holds application data, and rotate the key on your normal schedule. Azure supports two keys per account so you can rotate without downtime: update the endpoint to the second key, then regenerate the first. + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Container** | Yes | Name of the destination Azure Blob Storage container. | +| **Account** | Yes | Azure storage account name. | +| **Access key** | Yes | Access key for the storage account. | +| **Transformation** | Yes | JavaScript that defines the blob key, format, and data. | + + + +## Configure in the Dashboard + + + +## Prepare the container + +Create the storage account and container. Keep the account name and access key available. + + + + + +## Enter connection details + +Enter **Container**, **Account**, and **Access key**. + + + +## Configure the transformation + +Start from the provided object storage template. Change `config.key` to the prefix you want and keep `config.format` aligned with `data`. + + + +## Select event types + +Choose only the [event types](/platform/developer/webhooks/event-types) that you want to store. The connector processes selected events in batches. + + + +## Test the endpoint + +Create the endpoint. Open its **Testing** tab, choose a subscribed event type, and click **Send Example**. Confirm success in **Logs**, then verify that a timestamp-suffixed blob exists in the container and contains the expected data. + + + +## Verify delivery + +List the most recent blobs under your key prefix: + +```bash +az storage blob list \ + --account-name mystorageaccount \ + --container-name novu-events \ + --prefix novu-events/ \ + --query "[].name" \ + --output tsv +``` + +Download one blob and compare its contents with the transformation output in the endpoint's **Logs**: + +```bash +az storage blob download \ + --account-name mystorageaccount \ + --container-name novu-events \ + --name "novu-events/dt=2026-09-01/events-" \ + --file ./events.jsonl +``` + +## Troubleshooting + +- **Authentication fails**: Recheck the account name and access key. Confirm the key is active and can access the configured container. +- **The container is not found**: Confirm the container already exists in the specified storage account and that its spelling and casing match. +- **A successful delivery has unexpected contents**: Inspect the transformed output in **Logs** and verify that `format` and `data` agree. +- **A raw blob is invalid**: Return a string in `data` when `config.format` is `"raw"`. +- **Expected events are absent**: Check the endpoint's selected event types and the current Novu environment. + + + + +## Official references + +- [Svix object storage endpoints](https://docs.svix.com/advanced-endpoints/object-storage) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Azure Blob Storage documentation](https://learn.microsoft.com/azure/storage/blobs/) diff --git a/docs/guides/webhook-connectors/clickhouse.mdx b/docs/guides/webhook-connectors/clickhouse.mdx new file mode 100644 index 00000000000..c77af5ab0db --- /dev/null +++ b/docs/guides/webhook-connectors/clickhouse.mdx @@ -0,0 +1,165 @@ +--- +title: 'Insert webhook events into ClickHouse' +description: "Insert Novu webhook events into a ClickHouse table with the ClickHouse connector, including table setup, JSONEachRow column matching, and a batch transformation." +sidebarTitle: ClickHouse +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The ClickHouse connector inserts Novu webhook events into a ClickHouse table over the ClickHouse HTTP interface. Use it when you want notification events in ClickHouse for analytics without running your own ingestion service. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [ClickHouse advanced endpoint](https://docs.svix.com/advanced-endpoints/clickhouse). + + + +## Prerequisites + +- A ClickHouse server reachable over its HTTP or HTTPS interface, for example port `8443` for HTTPS or `8123` for HTTP. The native protocol port (`9000`) is not used. +- A ClickHouse user with a password and `INSERT` permission on the destination table. +- The destination table created before you enable the endpoint. The connector does not create or alter tables. + +## Configuration + +| Field | Required | Description | +| --- | --- | --- | +| URL | Yes | HTTP URL of the ClickHouse server, for example `https://clickhouse.example.com:8443`. | +| Username | Yes | ClickHouse user used to authenticate. | +| Password | Yes | Password for that user. | +| Table name | Yes | Table that receives the rows. | +| Database | No | Database that contains the table. Defaults to `default`. | +| Transformation | No | JavaScript that maps each webhook batch to rows. Required when your table columns do not match the top-level keys of the webhook body. | + +## Grant insert permission + +Give the connector user insert access on the destination table only. ClickHouse [`GRANT`](https://clickhouse.com/docs/sql-reference/statements/grant) syntax: + +```sql +GRANT INSERT ON analytics.novu_events TO novu_writer; +``` + +## Default destination behavior + +Without a transformation, Svix inserts each webhook payload directly using the ClickHouse [`JSONEachRow`](https://clickhouse.com/docs/interfaces/formats/JSONEachRow) format. Top-level payload fields are matched to columns by name. + +ClickHouse is different from the other warehouse connectors in one important way: it does not add any columns of its own. There is no generated `id` column and no `payload` column unless you define them. You own the full schema. + +Novu webhook bodies are nested, and message fields such as `subscriberId` and `channel` sit under `data.object` rather than at the top level. Because of that, plan on writing a transformation for this connector unless your table columns exactly match the top-level keys of the webhook body. + + +## Create the destination table + +This table stores a few flattened fields plus the full webhook body: + +```sql +CREATE TABLE novu_events ( + event_type String, + subscriber_id String, + channel String, + payload String, + received_at DateTime DEFAULT now() +) +ENGINE = MergeTree() +ORDER BY (event_type, subscriber_id); +``` + +`received_at` is not returned by the transformation below. Columns omitted from a `JSONEachRow` row take their declared default, so ClickHouse fills the insert time. + +## Transformation contract + +The transformation receives one batch and returns the rows to insert: + + +- Output: an object with a `rows` array. Each row is an object whose keys match your ClickHouse column names. Each row is inserted separately using `JSONEachRow`. + +Novu-oriented example that reads message fields from `event.payload.data.object`: + +```javascript +function handler(input) { + const rows = input.events.map((event) => { + const message = event.payload.data?.object ?? {}; + + return { + event_type: event.eventType, + subscriber_id: String(message.subscriberId ?? ""), + channel: String(message.channel ?? ""), + payload: JSON.stringify(event.payload), + }; + }); + + return { rows }; +} +``` + +Stringify nested values. A row value that is itself an object or array will not insert into a `String` column. + +## Configure in the Dashboard + + + + + + +## Enter connection details + +Enter the URL, username, password, table name, and database. + + + +## Configure the transformation + +Paste the transformation above and adjust the keys so they match your column names exactly. + + + +## Select event types + +Subscribe to the [event types](/platform/developer/webhooks/event-types) you want to store. If you subscribe to more than one, confirm the transformation produces valid rows for every one of them, since a single table receives all of them. + + + +## Test the endpoint + +Click **Create**, open the endpoint, go to the **Testing** tab, and use **Send Example** for each subscribed event type. Confirm success in the **Logs** tab. + + + + +## Verify delivery + +Query the table with `clickhouse-client`: + +```bash +clickhouse-client --host clickhouse.example.com --secure \ + --user novu_writer --password "$CLICKHOUSE_PASSWORD" \ + --query "SELECT event_type, subscriber_id, channel, received_at FROM analytics.novu_events ORDER BY received_at DESC LIMIT 10" +``` + +Or over the same HTTP interface the connector uses: + +```bash +curl -u "novu_writer:$CLICKHOUSE_PASSWORD" \ + --data-urlencode "query=SELECT count() FROM analytics.novu_events" \ + "https://clickhouse.example.com:8443/" +``` + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| Insert fails on an unknown field | The transformation returned a key that has no matching column. Remove the key or add the column. | +| Type errors on insert | A row value is an object or array being written to a scalar column. Wrap it in `JSON.stringify`. | +| Connection or timeout errors | The URL points at the native protocol port instead of the HTTP interface, or the server is not reachable from Novu. Use the HTTP or HTTPS port. | +| Authentication failures | Username or password is wrong, or the user lacks `INSERT` on the table. | +| Rows appear for some event types but not others | The transformation assumes fields that only exist on certain event types. Guard with defaults, as in the example above. | +| Table does not exist errors | The table must exist before the endpoint is enabled, and `database` defaults to `default` when left blank. | + + + +## Official references + +- [Svix ClickHouse endpoint](https://docs.svix.com/advanced-endpoints/clickhouse) +- [ClickHouse JSONEachRow format](https://clickhouse.com/docs/interfaces/formats/JSONEachRow) +- [ClickHouse access rights](https://clickhouse.com/docs/operations/access-rights) diff --git a/docs/guides/webhook-connectors/data-warehouse-sync.mdx b/docs/guides/webhook-connectors/data-warehouse-sync.mdx new file mode 100644 index 00000000000..8d70810fc86 --- /dev/null +++ b/docs/guides/webhook-connectors/data-warehouse-sync.mdx @@ -0,0 +1,68 @@ +--- +title: 'Export notification events to a data warehouse' +description: 'Stream Novu delivery and engagement events to BigQuery or another data warehouse for notification analytics.' +sidebarTitle: Plan your export +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; + +Novu can stream notification events to a data warehouse through an outbound webhook connector. You can then analyze delivery and engagement alongside your product data. + +This page covers the decisions to make before you configure an export. The connector guide for your warehouse owns the table schema, the transformation, and the verification query. + + + +## What the connector exports + +The endpoint receives the [webhook event types](/platform/developer/webhooks/event-types) you select. For notification analytics, these message events are usually the most useful: + +- `message.sent` +- `message.delivered` +- `message.failed` +- `message.seen` +- `message.read` +- `message.archived` + +Start with `message.sent`, `message.delivered`, and `message.failed` for delivery reporting, and add `message.seen`, `message.read`, and `message.archived` if you report on Inbox engagement. You can also subscribe to preference and workflow events. + +Each event arrives as a separate item in a delivery batch, and the connector transformation decides how many warehouse rows that batch produces. The default is one row per event. + + + Connectors stream events emitted after you create the endpoint. They do not backfill historical messages or create a current-state snapshot. + + +## Choose a destination + +Each warehouse has its own authentication model, default column layout, and transformation output. Open the guide for your destination and follow it end to end. + +| Warehouse | Guide | +| --- | --- | +| Google BigQuery | [Stream webhook events into Google BigQuery](/guides/webhook-connectors/google-bigquery) | +| ClickHouse | [Insert webhook events into ClickHouse](/guides/webhook-connectors/clickhouse) | +| Snowflake | [Load webhook events into Snowflake](/guides/webhook-connectors/snowflake) | +| Amazon Redshift | [Load webhook events into Amazon Redshift](/guides/webhook-connectors/amazon-redshift) | +| Postgres | [Insert webhook events into Postgres](/guides/webhook-connectors/postgres) | + +If you want files rather than rows, an [object storage connector](/platform/developer/webhooks/connectors#object-storage) writes each batch as an object that you can load on your own schedule. + +## Plan the schema before you enable the endpoint + +Connectors insert into a table that already exists. They do not create or alter tables, so a schema mismatch shows up as a failed delivery rather than a migration. + +Two decisions matter more than the rest: + +**Keep the full webhook body in one column.** Every connector guide's example stores the complete payload as JSON or a string alongside a small number of extracted columns. New fields in a Novu payload then land in the warehouse without a schema change, and you promote fields into views once you have seen real rows from your own environment. + +**Add an insert-time timestamp column.** Novu payload fields vary by event type, so a column your transformation always sets is the reliable partition and sort key. The connector examples use `received_at`. + +Do not treat event payloads as a stable contract for extracted columns. Send one example of each event type you plan to subscribe to, inspect what actually arrives, and extract only fields that are present across the event types you store. + +## Create one endpoint per environment + +Webhook endpoints are scoped to a Novu environment. Development and Production do not share endpoints, so create the endpoint in each environment whose events you want to export, and point non-production environments at a separate table or dataset. + +## Verify and troubleshoot + +Each connector guide ends with a verification query and a troubleshooting section for that destination. In every case, the endpoint's **Testing** tab sends an example for a subscribed event type, and the **Logs** tab shows the transformed payload and the destination's error message for a failed attempt. + +Failed deliveries follow the standard [webhook retry and recovery behavior](/platform/developer/webhooks/webhooks#recovering-and-resending-failed-messages). diff --git a/docs/guides/webhook-connectors/fifo-endpoint.mdx b/docs/guides/webhook-connectors/fifo-endpoint.mdx new file mode 100644 index 00000000000..c3bcf37d174 --- /dev/null +++ b/docs/guides/webhook-connectors/fifo-endpoint.mdx @@ -0,0 +1,140 @@ +--- +title: 'Receive webhook events in FIFO order' +description: 'Configure a FIFO webhook endpoint to receive selected Novu events in ordered HTTP batches.' +sidebarTitle: 'FIFO Endpoint' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +A FIFO endpoint sends Novu webhook events to an HTTP receiver in strict first-in, first-out order. Novu waits for a successful acknowledgement before sending the next batch. + + + +Strict ordering limits throughput because one failed or slow batch blocks later batches. Use FIFO only when the receiver must process events sequentially. + +## Prerequisites + +- A publicly reachable HTTPS receiver +- A receiver that can process the entire request body and return a successful HTTP response +- Permission to manage webhook endpoints in the Novu environment + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Endpoint URL** | Yes | HTTP receiver that accepts each transformed batch. | +| **Transformation** | Yes | JavaScript that converts the batch into one raw request body string. | +| Batching controls | No | Dashboard controls for how many messages are grouped into a delivery and how long the endpoint waits before sending. | + +Batching is configured on the endpoint rather than in the transformation. Use the labels and limits shown in your Novu Dashboard, and do not hard-code an assumed batch size in the receiver. + +## Transformation contract + +FIFO transformations run once per batch. + + + +It must return: + +```ts +{ + requestBody: string; +} +``` + +This verified example collects the batch into a single JSON request body: + +```js +function handler(input) { + const events = input.events.map((event) => ({ + eventType: event.eventType, + payload: event.payload, + })); + + return { + requestBody: JSON.stringify({ data: events }), + }; +} +``` + +The receiver gets one HTTP request whose raw body has this shape: + +```json +{ + "data": [ + { + "eventType": "message.sent", + "payload": { + "type": "message.sent", + "object": "message", + "data": { + "object": { + "subscriberId": "subscriber-123" + } + } + } + } + ] +} +``` + +Your receiver must acknowledge the current batch successfully before the next batch can be delivered. The official FIFO documentation does not define a numeric batch default. + +## Configure in the Dashboard + + + +## Prepare the receiver + +Create an HTTPS route that reads the raw batch body, completes processing, and returns a successful response only when the batch is safe to advance. + + + + + +## Enter connection details + +Enter the endpoint URL. Configure the batching controls shown in the Dashboard based on the maximum latency and batch size your receiver can handle. + + + +## Configure the transformation + +Review the transformation. Ensure `handler(input)` handles every element in `input.events` and returns `requestBody` as a string. + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) that require strict ordering. Avoid unrelated events that could block the same delivery sequence. + + + +## Test the endpoint + +Create the endpoint. Send multiple examples from **Testing** and inspect **Logs**. Confirm the receiver gets each batch only after the preceding request succeeds and that the body matches the transformation output. + + + +## Verify delivery + +Confirm the receiver logged one request whose body matches the transformed batch. Send two tests in sequence and verify the second request arrives only after the first returns a successful response. + +## Troubleshooting + +- **Later events stop arriving**: Inspect the earliest failed or timed-out batch. FIFO delivery cannot advance until the current batch succeeds. +- **The receiver cannot parse the body**: Confirm the transformation returns `requestBody` as a string containing valid data for your receiver. +- **Throughput is too low**: Review the Dashboard batching settings and receiver latency. Strict acknowledgement sequencing limits throughput by design. +- **Events inside a batch disappear**: Ensure the transformation maps every `input.events` entry that the receiver needs. +- **Events arrive at the wrong endpoint**: Confirm the endpoint URL and Novu environment before replaying failures. + + + + +## Official references + +- [Svix FIFO endpoints](https://docs.svix.com/advanced-endpoints/fifo-endpoints) +- [How FIFO webhook delivery works](https://www.svix.com/blog/fifo-ordered-webhooks-delivery/) diff --git a/docs/guides/webhook-connectors/google-bigquery.mdx b/docs/guides/webhook-connectors/google-bigquery.mdx new file mode 100644 index 00000000000..fbd7b297aa4 --- /dev/null +++ b/docs/guides/webhook-connectors/google-bigquery.mdx @@ -0,0 +1,170 @@ +--- +title: 'Stream webhook events into Google BigQuery' +description: "Stream Novu webhook events into a BigQuery table with the BigQuery connector, including service account permissions, the default id and payload schema, and a batch transformation." +sidebarTitle: Google BigQuery +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Google BigQuery connector inserts Novu webhook events as rows in a BigQuery table. Use it when you want notification data in BigQuery for reporting or downstream pipelines without building an ingestion service. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [BigQuery advanced endpoint](https://docs.svix.com/advanced-endpoints/bigquery). + + + +## Prerequisites + +- A GCP project with a BigQuery dataset. +- The destination table created before you enable the endpoint. The connector does not create or alter tables. +- A Google Cloud service account with a JSON key, granted write access on the destination dataset. + +## Configuration + +| Field | Required | Description | +| --- | --- | --- | +| Project ID | Yes | The GCP project that owns the dataset. | +| Dataset ID | Yes | The BigQuery dataset that contains the table. | +| Table ID | Yes | The table that receives the rows. | +| Credentials | Yes | Google Cloud service account credentials JSON, provided as a string. | +| Transformation | No | JavaScript that maps each webhook batch to rows. Required when your table does not use the default two column schema. | + +Paste the full contents of the service account JSON key file into the credentials field. Treat it as a secret and do not commit it to a repository. + +## Service account permissions + +Svix does not publish a required role list for this connector, so scope the service account using Google's own reference. Google documents that writing rows to a table requires the `bigquery.tables.updateData` permission, which is included in the predefined `roles/bigquery.dataEditor` role. See [BigQuery access control](https://cloud.google.com/bigquery/docs/access-control). + +Grant the role on the destination dataset rather than the whole project: + +```bash +bq add-iam-policy-binding \ + --member="serviceAccount:novu-writer@my-gcp-project.iam.gserviceaccount.com" \ + --role="roles/bigquery.dataEditor" \ + my-gcp-project:my_dataset +``` + +If inserts fail with a permission error, compare the failure in the endpoint **Logs** tab against the permissions listed on the Google page above before widening access. + +## Default destination behavior + +Without a transformation, Svix writes two columns. It generates a unique `id` for each row and writes the raw webhook payload to `payload`: + +```sql +CREATE TABLE `my-gcp-project.my_dataset.events` ( + id STRING, + payload STRING +); +``` + +The table must exist before you enable the endpoint. With a transformation you control every column, so the table can use any schema that matches the rows you return. + + +## Create an analytics table + +```sql +CREATE TABLE `my-gcp-project.my_dataset.novu_events` ( + id STRING NOT NULL, + event_type STRING NOT NULL, + subscriber_id STRING, + channel STRING, + payload STRING NOT NULL, + received_at TIMESTAMP NOT NULL +) +PARTITION BY DATE(received_at) +CLUSTER BY event_type; +``` + +## Transformation contract + +The transformation receives one batch and returns the rows to insert: + + +- Output: an object with a `rows` array. Each row is inserted as a separate BigQuery row, and its keys must match your column names. + +Novu-oriented example that matches the analytics table above and reads message fields from `event.payload.data.object`: + +```javascript +function handler(input) { + const rows = input.events.map((event) => { + const message = event.payload.data?.object ?? {}; + + return { + id: crypto.randomUUID(), + event_type: event.eventType, + subscriber_id: message.subscriberId ?? null, + channel: message.channel ?? null, + payload: JSON.stringify(event.payload), + received_at: new Date().toISOString(), + }; + }); + + return { rows }; +} +``` + +The default template Svix ships returns only `id` and `payload`, which matches the two column schema. Replace it when you use a wider table. + +## Configure in the Dashboard + + + + + + +## Enter connection details + +Enter the project ID, dataset ID, table ID, and paste the service account credentials JSON. + + + +## Configure the transformation + +Keep the default template if your table uses the `id` and `payload` schema. Otherwise paste a transformation whose row keys match your columns. + + + +## Select event types + +Subscribe to the [event types](/platform/developer/webhooks/event-types) you want to store. All subscribed types land in the same table, so confirm the transformation produces valid rows for each one. + + + +## Test the endpoint + +Click **Create**, open the endpoint, go to the **Testing** tab, and use **Send Example** for each subscribed event type. Confirm success in the **Logs** tab. + + + + +## Verify delivery + +```bash +bq query --use_legacy_sql=false \ + 'SELECT event_type, subscriber_id, channel, received_at + FROM `my-gcp-project.my_dataset.novu_events` + ORDER BY received_at DESC + LIMIT 10' +``` + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| Delivery succeeds in **Logs** but the query returns nothing | Confirm the project, dataset, and table in the query match the endpoint. Then compare the transformed payload in **Logs** with the table schema. | +| Permission denied on insert | The service account is missing `bigquery.tables.updateData` on the table, or the binding was made on the wrong dataset. | +| Invalid credentials errors | The credentials field must contain the full JSON key as a string. A truncated or re-quoted key fails. | +| Table not found | The table must exist before the endpoint is enabled, and the project, dataset, and table fields must match it exactly. | +| Invalid value for a column | A `TIMESTAMP` column received a value that is not a valid timestamp, or a required column received `null`. The example returns ISO 8601 strings for `received_at`. | +| No event type to filter on | You are using the default two column schema. Switch to the analytics table and transformation above. | +| Rows appear for some event types but not others | The transformation assumes fields that only exist on certain event types. Guard with defaults, as in the example above. | + + + +## Official references + +- [Svix BigQuery endpoint](https://docs.svix.com/advanced-endpoints/bigquery) +- [BigQuery access control](https://cloud.google.com/bigquery/docs/access-control) +- [BigQuery JSON functions](https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions) diff --git a/docs/guides/webhook-connectors/google-cloud-pubsub.mdx b/docs/guides/webhook-connectors/google-cloud-pubsub.mdx new file mode 100644 index 00000000000..5bb6d8bda46 --- /dev/null +++ b/docs/guides/webhook-connectors/google-cloud-pubsub.mdx @@ -0,0 +1,140 @@ +--- +title: 'Publish webhook events to Google Cloud Pub/Sub' +description: 'Configure the Google Cloud Pub/Sub webhook connector and verify selected Novu events in a subscription.' +sidebarTitle: 'Google Cloud Pub/Sub' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Google Cloud Pub/Sub connector publishes selected Novu webhook events to a topic in your Google Cloud project. + + + +## Prerequisites + +- A Google Cloud project +- An existing Pub/Sub topic, created before you configure the endpoint +- A service account with a JSON key that can publish to that topic +- A subscription attached to the topic so you can pull messages during verification + +### Required permission + +Publishing requires the `pubsub.topics.publish` permission, which is included in the predefined `roles/pubsub.publisher` role. Grant it on the topic rather than the project: + +```bash +gcloud pubsub topics add-iam-policy-binding novu-events \ + --member="serviceAccount:novu-publisher@my-gcp-project.iam.gserviceaccount.com" \ + --role="roles/pubsub.publisher" +``` + +See [Pub/Sub access control](https://cloud.google.com/pubsub/docs/access-control) for the full role reference. Paste the complete JSON key into the credentials field and treat it as a secret. + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Project ID** | Yes | Google Cloud project that owns the topic. | +| **Topic** | Yes | Pub/Sub topic that receives the messages. | +| **Credentials** | Yes | Google Cloud credentials JSON used to connect to the project. | +| **Transformation** | Yes | Dashboard-provided JavaScript that shapes the published message. | + +## Transformation contract + + + +The return shape is defined by the template the Novu Dashboard ships with this connector, and it differs from the return shape of the queue and object storage connectors. Read the JSDoc comment above the template's `handler` for the exact keys, and do not paste a handler from another connector page. + +To customize it safely: + +1. Send an example event without changing the template. +2. Inspect the transformation output in the endpoint's **Logs**. +3. Pull the corresponding message from a Pub/Sub subscription. +4. Change one field at a time and repeat the test. + +A decoded subscriber message can represent the selected Novu event like this: + +```json +{ + "eventType": "message.sent", + "payload": { + "type": "message.sent", + "object": "message", + "data": { + "object": { + "subscriberId": "subscriber-123", + "channel": "email" + } + } + } +} +``` + +Use the endpoint **Logs** and the pulled Pub/Sub message to confirm the exact shape before changing the transformation. + +## Configure in the Dashboard + + + +## Prepare Pub/Sub + +Create the topic and attach a subscription that you can pull from during testing. Prepare credentials JSON that can publish to the topic. + + + + + +## Enter connection details + +Enter **Project ID**, **Topic**, and **Credentials**. + + + +## Configure the transformation + +Preserve the Dashboard template for the first test. After it succeeds, change one field at a time and compare the published message with the endpoint log. + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) to publish. Start with one event type so the first subscriber message is easy to identify. + + + +## Test the endpoint + +Create the endpoint. Open **Testing**, select a subscribed event type, and click **Send Example**. Confirm success in **Logs**, pull the message from your subscription, decode its data as JSON, and compare it with the logged transformation output. + + + +## Verify delivery + +Pull one message from the verification subscription without acknowledging it: + +```bash +gcloud pubsub subscriptions pull novu-events-verify \ + --limit=1 \ + --format="value(message.data)" | base64 --decode +``` + +Pub/Sub message data is base64 encoded on the wire, so decode it before comparing with the transformation output shown in the endpoint's **Logs**. If the pull returns nothing while **Logs** reports success, another subscriber likely acknowledged the message first. Use a dedicated verification subscription with no active consumers. + +## Troubleshooting + +- **Authentication fails**: Validate the credentials JSON and confirm it belongs to the configured project. +- **The topic is not found**: Check the exact project ID and topic name. Confirm the topic already exists. +- **Novu reports success but the subscriber is empty**: Confirm the subscription is attached to the configured topic and inspect subscription filters or retention settings. +- **The message shape is unexpected**: Restore the Dashboard template, send another example, and compare the endpoint log with the decoded subscriber message. +- **Some event types never publish**: Review the endpoint's selected event types and test each one separately. + + + + +## Official references + +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Google Cloud Pub/Sub documentation](https://cloud.google.com/pubsub/docs) +- [Pub/Sub access control](https://cloud.google.com/pubsub/docs/access-control) diff --git a/docs/guides/webhook-connectors/google-cloud-storage.mdx b/docs/guides/webhook-connectors/google-cloud-storage.mdx new file mode 100644 index 00000000000..97dc8954afe --- /dev/null +++ b/docs/guides/webhook-connectors/google-cloud-storage.mdx @@ -0,0 +1,108 @@ +--- +title: 'Send webhook events to Google Cloud Storage' +description: 'Configure the Google Cloud Storage webhook connector to store selected Novu events as batched objects.' +sidebarTitle: 'Google Cloud Storage' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import ObjectStorageTransformation from "/snippets/object-storage-transformation.mdx"; + +The Google Cloud Storage connector writes selected Novu webhook events to a bucket. Each delivered batch creates a new object. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [object storage advanced endpoint](https://docs.svix.com/advanced-endpoints/object-storage), which is shared with Amazon S3 and Azure Blob Storage. + + + +## Prerequisites + +- An existing Google Cloud Storage bucket, created before you configure the endpoint +- A Google Cloud service account with a JSON key that can create objects in that bucket + +### Required permission + +Writing a new object requires the `storage.objects.create` permission, which is included in the predefined `roles/storage.objectCreator` role. That role can create objects but cannot read, overwrite, or delete them, which is usually what an append-only event archive wants. Grant it on the bucket rather than the project: + +```bash +gcloud storage buckets add-iam-policy-binding gs://novu-events \ + --member="serviceAccount:novu-writer@my-gcp-project.iam.gserviceaccount.com" \ + --role="roles/storage.objectCreator" +``` + +See [Cloud Storage IAM roles](https://cloud.google.com/storage/docs/access-control/iam-roles) for the full permission reference. Paste the complete JSON key into the credentials field and treat it as a secret. + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Bucket** | Yes | Name of the destination Google Cloud Storage bucket. | +| **Credentials** | Yes | Google Cloud service account credentials JSON, supplied as a string. | +| **Transformation** | Yes | JavaScript that defines the object key, format, and data. | + + + +## Configure in the Dashboard + + + +## Prepare the bucket + +Create the bucket and service account credentials. Keep the complete credentials JSON available. + + + + + +## Enter connection details + +Enter **Bucket** and the service account **Credentials** JSON. + + + +## Configure the transformation + +Start from the provided object storage template. Change `config.key` to the prefix you want and keep `config.format` aligned with `data`. + + + +## Select event types + +Select the [event types](/platform/developer/webhooks/event-types) to store. Selecting fewer event types also limits which events reach the transformation. + + + +## Test the endpoint + +Create the endpoint. In **Testing**, send an example for a subscribed event type. Confirm success in **Logs**, then verify the bucket contains a new timestamp-suffixed object with the expected content. + + + +## Verify delivery + +List objects under your key prefix, then read one and compare it with the transformation output in the endpoint's **Logs**: + +```bash +gcloud storage ls "gs://novu-events/novu-events/**" + +gcloud storage cat "gs://novu-events/novu-events/dt=2026-09-01/events-" +``` + +If `roles/storage.objectCreator` is the only role you granted, the connector's service account cannot list or read objects. Run these commands with your own credentials rather than the connector's key. + +## Troubleshooting + +- **Credentials are rejected**: Paste the complete service account credentials JSON and confirm it is valid JSON. +- **The bucket cannot be accessed**: Confirm the bucket exists and the supplied service account can write to it. +- **No object appears after a successful test**: Check the exact bucket name and inspect the transformed output in **Logs**. +- **Object contents are malformed**: Confirm `config.format` matches `data`. For `"raw"`, return a string. +- **Events are missing**: Confirm the endpoint subscribes to those event types in the intended Novu environment. + + + + +## Official references + +- [Svix object storage endpoints](https://docs.svix.com/advanced-endpoints/object-storage) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [Cloud Storage IAM roles](https://cloud.google.com/storage/docs/access-control/iam-roles) diff --git a/docs/guides/webhook-connectors/opentelemetry-collector.mdx b/docs/guides/webhook-connectors/opentelemetry-collector.mdx new file mode 100644 index 00000000000..42f363efed4 --- /dev/null +++ b/docs/guides/webhook-connectors/opentelemetry-collector.mdx @@ -0,0 +1,147 @@ +--- +title: 'Send webhook events to an OpenTelemetry Collector' +description: 'Configure the OpenTelemetry Collector webhook connector to convert selected Novu events into OTLP trace spans.' +sidebarTitle: 'OpenTelemetry Collector' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The OpenTelemetry Collector connector transforms selected Novu webhook events into spans and sends them to an OTLP HTTP traces endpoint. + + + +## Prerequisites + +- An OpenTelemetry-compatible traces endpoint +- Any authentication headers required by the collector or observability provider +- Permission to manage webhook endpoints in the Novu environment + +The URL is typically the provider's `OTEL_EXPORTER_OTLP_ENDPOINT` with `/v1/traces` appended. + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **URL** | Yes | OTLP HTTP traces endpoint, including `/v1/traces` when required by the provider. | +| **Headers** | No | String key-value headers required by the collector. | +| **Transformation** | Yes | JavaScript that maps an event batch to OpenTelemetry spans. | + +Provider-specific examples documented by Svix include `dd-api-key` for Datadog, `CX-Application-Name` and `CX-Subsystem-Name` for Coralogix, and `api-key` for New Relic. Confirm current requirements with your provider. + +## Transformation contract + + + +It returns: + +```ts +{ + config: { + serviceName: string; + scope: { + name: string; + version: string; + }; + }; + spans: Array<{ + startTime: string; + endTime: string; + name: string; + kind?: "SERVER" | "CLIENT" | "PRODUCER" | "CONSUMER" | "INTERNAL"; + attributes?: Record; + traceIdKey?: string; + spanIdKey?: string; + parentSpanIdKey?: string; + }>; +} +``` + +`startTime` and `endTime` must be ISO 8601 strings. `traceIdKey` groups spans into one trace. `spanIdKey` identifies a parent span, and `parentSpanIdKey` attaches a child to that parent. + +```js +function handler(input) { + const spans = input.events.map((event) => { + const timestamp = new Date().toISOString(); + + return { + startTime: timestamp, + endTime: timestamp, + name: event.eventType, + attributes: event.payload, + }; + }); + + return { + config: { + serviceName: "novu.webhooks", + scope: { + name: "novu.webhooks", + version: "1.0.0", + }, + }, + spans, + }; +} +``` + +This default shape creates one span per event. To correlate events, inspect the real Novu test payloads you select, derive stable keys from them, and set `traceIdKey`, `spanIdKey`, and `parentSpanIdKey`. The child field is `parentSpanIdKey`. + +## Configure in the Dashboard + + + +## Prepare the collector + +Copy the OTLP traces URL and required headers from your collector or observability provider. + + + + + +## Enter connection details + +Enter the **URL** and optional **Headers**. + + + +## Configure the transformation + +Map span times, identifiers, and attributes from your selected Novu payloads. Keep `config.serviceName` stable so traces remain searchable. + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) that should produce spans. Select related lifecycle events if you intend to correlate parent and child spans. + + + +## Test the endpoint + +Create the endpoint. Use **Testing** to send examples for the selected events. Confirm success in **Logs**, then search the observability platform for `service.name` set by `config.serviceName`. + + + +## Verify delivery + +Search the tracing backend for the configured service name and confirm one span appears for the test event. Compare its name, timestamps, and attributes with the transformed output in the endpoint's **Logs** tab. + +## Troubleshooting + +- **The collector returns an HTTP error**: Confirm the URL is an OTLP HTTP traces endpoint and includes `/v1/traces` where required. +- **Authentication fails**: Verify header names and values against the observability provider's current OTLP documentation. +- **No spans appear after success**: Search for the configured service name and confirm the provider is reading from the same account, site, or region. +- **Spans have invalid timestamps**: Return ISO 8601 strings for `startTime` and `endTime`. +- **Spans do not correlate**: Use the same `traceIdKey` for related spans and match a child's `parentSpanIdKey` to its parent's `spanIdKey`. + + + + +## Official references + +- [Svix OpenTelemetry tracing endpoint](https://docs.svix.com/advanced-endpoints/otel-tracing) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [OpenTelemetry OTLP endpoint configuration](https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/#otel_exporter_otlp_endpoint) diff --git a/docs/guides/webhook-connectors/polling-endpoint.mdx b/docs/guides/webhook-connectors/polling-endpoint.mdx new file mode 100644 index 00000000000..7daeec4696c --- /dev/null +++ b/docs/guides/webhook-connectors/polling-endpoint.mdx @@ -0,0 +1,140 @@ +--- +title: 'Consume webhook events from a polling endpoint' +description: 'Create a Novu polling endpoint, fetch selected webhook events by consumer ID, and commit processed offsets.' +sidebarTitle: 'Polling Endpoint' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; + +A polling endpoint lets a worker fetch Novu webhook events without exposing a public HTTP receiver. Each consumer tracks its own progress through the endpoint. + + + +## Prerequisites + +- Permission to manage webhook endpoints in the Novu environment +- A worker that can make authenticated HTTPS requests +- A stable, unique consumer ID for each independent worker + +## Configuration + +The Novu Dashboard provides these values after you create the endpoint: + +| Value | Required | Description | +| --- | --- | --- | +| **Polling URL** | Yes | Endpoint-specific URL ending in `/consumer/{consumer_id}`. | +| **API key** | Yes | Endpoint-scoped bearer token with an `sk_poll_` prefix. | +| **Consumer ID** | Yes | Arbitrary client identifier that tracks one independent position. | + +API keys are scoped to the polling endpoint and can be expired or rotated. Do not share one consumer ID across concurrent clients. Shared clients can drift to different positions and receive errors. + +## Consumption contract + +Copy the polling URL from the Dashboard and replace `{consumer_id}` with a stable identifier for the worker. Treat the Dashboard URL as authoritative, including its host: the examples below use the US host, and the application and endpoint identifiers are specific to your endpoint. + +```bash +curl \ + -X GET \ + "https://api.us.svix.com/api/v1/app/YOUR_APP_ID/polling-endpoint/YOUR_POLL_ID/consumer/worker-1" \ + -H "Accept: application/json" \ + -H "Authorization: Bearer YOUR_POLLING_API_KEY" +``` + +The response contains messages in the order they were received. Each message has an `offset`: + +```json +{ + "data": [ + { + "id": "msg_2K2N9Qk...", + "eventType": "message.sent", + "payload": { + "type": "message.sent", + "object": "message", + "data": { + "object": { + "subscriberId": "subscriber-123" + } + } + }, + "timestamp": "2025-01-17T00:00:00.000Z", + "offset": 0 + } + ], + "done": true +} +``` + +After processing a batch successfully, commit the last processed offset: + +```bash +curl \ + -X POST \ + "https://api.us.svix.com/api/v1/app/YOUR_APP_ID/polling-endpoint/YOUR_POLL_ID/consumer/worker-1/commit" \ + -H "Accept: application/json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_POLLING_API_KEY" \ + -d '{ "offset": 0 }' +``` + +Until the offset is committed, the same messages can be returned again after the lease expires. Process messages idempotently and commit only after your work succeeds. Transformations do not apply to polling consumption. + +## Configure in the Dashboard + + + + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) the worker should receive. Add channels if the Dashboard offers channel filtering for your endpoint. + + + +## Create the endpoint + +Create the endpoint. Copy its polling URL and create or copy the endpoint-specific API key. Store the key as a secret. + + + +## Fetch with a unique consumer + +Choose a stable consumer ID for the worker and call the polling URL. Use a different ID for every independent client. + + + +## Process and commit + +Process the returned messages in order. Commit the last successfully processed `offset`, then poll again. + + + +## Test the endpoint + +Send an example for a selected event type from **Testing**. Poll until it appears, process it, commit its offset, and confirm the next request does not return it again. + + + +## Verify delivery + +After processing and committing the test event, poll again with the same consumer ID. The committed event should not be returned. A different consumer ID starts with its own independent position. + +## Troubleshooting + +- **The request returns unauthorized**: Confirm the bearer token belongs to this polling endpoint and has not expired or been rotated. +- **The same messages return again**: Commit the final successfully processed offset. Replays can occur before a commit or after a lease expires. +- **Concurrent workers receive position errors**: Assign a unique consumer ID to each worker. Do not share consumer IDs. +- **No events are returned**: Confirm the endpoint subscribes to the event type, the event occurred after endpoint creation, and you are polling the intended Novu environment. +- **Events are skipped after a failure**: Commit only after processing succeeds, and commit the last offset that completed successfully. + +Polling endpoints use their own offset recovery model. + + + +## Official references + +- [Svix polling endpoints](https://docs.svix.com/advanced-endpoints/polling-endpoints) +- [Svix polling endpoint consumer guide](https://docs.svix.com/receiving/using-app-portal/polling-endpoints) diff --git a/docs/guides/webhook-connectors/postgres.mdx b/docs/guides/webhook-connectors/postgres.mdx new file mode 100644 index 00000000000..9e5f2cf8a69 --- /dev/null +++ b/docs/guides/webhook-connectors/postgres.mdx @@ -0,0 +1,171 @@ +--- +title: 'Insert webhook events into Postgres' +description: 'Insert Novu webhook events into a Postgres table with the Postgres connector, including least-privilege database grants, an analytics schema, and verification from the Novu Dashboard.' +sidebarTitle: Postgres +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Postgres connector writes Novu webhook events to a table in your Postgres database. Use it when notification events need to live in an operational database or feed a reporting pipeline that already reads from Postgres. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction), and this connector is one of its [advanced endpoint types](https://docs.svix.com/advanced-endpoints). + + + +## Prerequisites + +- A Postgres database reachable over the public internet +- The destination table created before you enable the endpoint. The connector does not create or alter tables. +- A dedicated database user that can connect to the database and insert into that table +- Network rules that allow the connection, including any cloud provider IP allowlist + + + Do not reuse an application owner or migration account. The connector only needs to insert into one table. + + +## Configuration + +| Field | Required | Description | +| --- | --- | --- | +| Host | Yes | Hostname of the Postgres server. | +| Port | Yes | Port the Postgres server listens on. | +| Database | Yes | Database that contains the destination table. | +| Username | Yes | Database user the connector authenticates as. | +| Password | Yes | Password for that user. | +| Table name | Yes | Table that receives the event rows. | +| Transformation | Yes | JavaScript that maps each webhook batch to rows for that table. | + +## Create the destination table + +This schema keeps the event type available for filtering, extracts the two fields most reports need, and preserves the complete webhook body: + +```sql +CREATE TABLE novu_events ( + id UUID PRIMARY KEY, + event_type TEXT NOT NULL, + subscriber_id TEXT, + channel TEXT, + payload JSONB NOT NULL, + received_at TIMESTAMPTZ NOT NULL +); + +CREATE INDEX novu_events_event_type_idx + ON novu_events (event_type); + +CREATE INDEX novu_events_received_at_idx + ON novu_events (received_at DESC); +``` + +`payload` holds the full body as `JSONB`, so a new field in a Novu payload does not require a migration. `received_at` is set by the transformation rather than by the payload, which makes it a reliable sort key across every event type. + +## Grant least-privilege access + +```sql +GRANT CONNECT ON DATABASE YOUR_DATABASE TO novu_webhook_writer; +GRANT USAGE ON SCHEMA public TO novu_webhook_writer; +GRANT INSERT ON TABLE public.novu_events TO novu_webhook_writer; +``` + +Replace the database, schema, user, and table names with your own. Add `SELECT` only if your verification process or database policy requires the same user to read rows back. + + +## Transformation contract + + + +The return shape is defined by the template the Novu Dashboard ships with this connector. Open the template and read the JSDoc comment above its `handler` for the exact keys, because the return shape differs between connector families. Map the values below into that template rather than pasting a handler from another connector page: + +| Column | Value to map | +| --- | --- | +| `id` | `crypto.randomUUID()` | +| `event_type` | `event.eventType` | +| `subscriber_id` | `event.payload.data?.object?.subscriberId ?? null` | +| `channel` | `event.payload.data?.object?.channel ?? null` | +| `payload` | `JSON.stringify(event.payload)` | +| `received_at` | `new Date().toISOString()` | + +Send an example event before you customize anything, read the transformed output in the endpoint's **Logs**, then change one column at a time and re-test. + +## Configure in the Dashboard + + + +## Prepare the database + +Create the table, create the restricted user, and apply the grants above. Confirm the user can connect and insert from outside your network. + + + + + +## Enter connection details + +Enter the host, port, database, username, password, and table name. Add a description that identifies the database and environment. + + + +## Configure the transformation + +Start from the transformation template shown in the Dashboard and map the columns using the table above. + + + +## Select event types + +Subscribe to the [event types](/platform/developer/webhooks/event-types) your consumer needs. All subscribed types insert into the same table, so confirm the transformation produces valid rows for each one. For delivery reporting, start with `message.sent`, `message.delivered`, and `message.failed`. + + + +## Test the endpoint + +Click **Create**, open the endpoint, go to the **Testing** tab, and use **Send Example** for each subscribed event type. Confirm success in the **Logs** tab. + + + +## Verify delivery + +```sql +SELECT + event_type, + subscriber_id, + channel, + received_at +FROM novu_events +ORDER BY received_at DESC +LIMIT 20; +``` + +To read a field that you did not extract into a column, query the stored body: + +```sql +SELECT payload #>> '{data,object,subscriberId}' AS subscriber_id +FROM novu_events +ORDER BY received_at DESC +LIMIT 20; +``` + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| Connection refused or timed out | Hostname, port, firewall rules, and any cloud provider IP allowlist. | +| Authentication failed | The username and password, and whether that user is allowed to connect to the selected database. | +| Permission denied on insert | The user needs `USAGE` on the schema and `INSERT` on the destination table. | +| Relation does not exist | The table must exist before the endpoint is enabled, and an unqualified name resolves through the user's `search_path`. | +| Column or type error | Compare the transformed output in **Logs** with the table definition. A `TIMESTAMPTZ` column needs a valid timestamp, and a `NOT NULL` column cannot receive `null`. | +| Subscriber or channel values are empty | Message fields are under `payload.data.object`, not at the top level of `payload`. `payload.object` is the resource type string, such as `"message"`. | +| Rows appear for some event types but not others | The transformation assumes fields that only exist on certain event types. Guard each one with a default. | +| No rows after a successful delivery | Confirm the database, schema, and table you queried match the endpoint configuration. | + +Use the **Logs** tab to inspect transformed payloads and delivery attempts. + + + +## Official references + +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [PostgreSQL GRANT](https://www.postgresql.org/docs/current/sql-grant.html) +- [PostgreSQL JSON functions and operators](https://www.postgresql.org/docs/current/functions-json.html) diff --git a/docs/guides/webhook-connectors/rabbitmq.mdx b/docs/guides/webhook-connectors/rabbitmq.mdx new file mode 100644 index 00000000000..58bedf15ae0 --- /dev/null +++ b/docs/guides/webhook-connectors/rabbitmq.mdx @@ -0,0 +1,152 @@ +--- +title: 'Publish webhook events to RabbitMQ' +description: 'Configure the RabbitMQ webhook connector to publish selected Novu events as individual AMQP messages.' +sidebarTitle: 'RabbitMQ' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The RabbitMQ connector publishes each selected Novu webhook event as a separate message using one routing key. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [RabbitMQ advanced endpoint](https://docs.svix.com/advanced-endpoints/rabbitmq). + + + +## Prerequisites + +- A RabbitMQ instance reachable over AMQP from the public internet +- An AMQP connection URI that includes credentials and the virtual host +- A routing key that resolves to the queue you want to consume from +- A RabbitMQ user with configure, write, and read permissions scoped to that virtual host + +The connector takes a connection URI and a routing key. There is no separate exchange field, so encode the credentials and virtual host in the URI and make sure the routing key resolves through the default exchange or an existing binding. + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **URI** | Yes | AMQP connection URI, such as `amqp://user:password@rabbitmq.example.com:5672/my-vhost`. | +| **Routing key** | Yes | Routing key used for every published message. | +| **Transformation** | Yes | JavaScript that returns the message bodies to publish. | + +## Transformation contract + + + +It returns an array of strings: + +```ts +{ + payloads: string[]; +} +``` + +Each string in `payloads` becomes a separate RabbitMQ message. The default transformation serializes each event: + +```js +function handler(input) { + const payloads = input.events.map((event) => JSON.stringify(event)); + + return { + payloads, + }; +} +``` + +With the default transformation, a `message.sent` event is published as a single message whose body contains both the event type and the Novu payload: + +```json +{"payload":{"type":"message.sent","object":"message","data":{"object":{"subscriberId":"subscriber-123","channel":"email"}}},"eventType":"message.sent"} +``` + +### Novu example + +Consumers usually want the event type and the message fields at the top level so they can route without walking the nested payload. Novu message fields are under `payload.data.object`: + +```js +function handler(input) { + const payloads = input.events.map((event) => { + const message = event.payload.data?.object ?? {}; + + return JSON.stringify({ + eventType: event.eventType, + subscriberId: message.subscriberId ?? null, + channel: message.channel ?? null, + payload: event.payload, + }); + }); + + return { + payloads, + }; +} +``` + +Guard every extracted field with a default. Preference and workflow events do not carry the same properties as message events, and one selected event type with a missing field fails the whole batch. + +Return fewer strings than `input.events.length` to filter events in the transformation, or a single string to combine a batch into one message. The connector publishes each returned string separately and does not guarantee ordering, so consumers should treat messages as independent. + +## Configure in the Dashboard + + + +## Prepare RabbitMQ + +Create the required RabbitMQ routing and credentials. Confirm the connection URI works from a network location allowed by your RabbitMQ deployment. + + + + + +## Enter connection details + +Enter the **URI** and **Routing key**. + + + +## Configure the transformation + +Start from the provided template. Return one serialized string in `payloads` for each RabbitMQ message you want to publish. + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) to publish. Ensure the transformation returns a valid string for every selected event. + + + +## Test the endpoint + +Create the endpoint. In **Testing**, send an example for a subscribed event type. Confirm success in **Logs**, then consume from the bound queue and compare the message body with the transformed payload. + + + +## Verify delivery + +Read one message from the destination queue without acknowledging it: + +```bash +rabbitmqadmin get queue=novu-events count=1 ackmode=reject_requeue_true +``` + +Compare the returned payload with the transformation output in the endpoint's **Logs**. If the queue is empty while **Logs** shows a successful delivery, the routing key did not resolve to this queue. Check the binding before you change the transformation. + +## Troubleshooting + +- **The connector cannot connect**: Check the URI scheme, host, port, credentials, and encoded virtual host. Confirm the RabbitMQ service is reachable. +- **Authentication or authorization fails**: Confirm the URI credentials are active and allowed to publish through the configured virtual host and routing. +- **Delivery succeeds but no queue receives a message**: Check that the routing key is bound to the intended queue. +- **RabbitMQ receives invalid content**: Return strings in `payloads`. Use `JSON.stringify` for JSON message bodies. +- **A selected event is absent**: Confirm the transformation creates one `payloads` entry for that event and review the endpoint event selection. + + + +## Official references + +- [Svix RabbitMQ endpoint](https://docs.svix.com/advanced-endpoints/rabbitmq) +- [Svix advanced endpoint types](https://docs.svix.com/advanced-endpoints) +- [RabbitMQ documentation](https://www.rabbitmq.com/docs) diff --git a/docs/guides/webhook-connectors/segment.mdx b/docs/guides/webhook-connectors/segment.mdx new file mode 100644 index 00000000000..3a514b41177 --- /dev/null +++ b/docs/guides/webhook-connectors/segment.mdx @@ -0,0 +1,142 @@ +--- +title: 'Send webhook events to Segment' +description: 'Configure the Segment webhook connector to map selected Novu events to Segment Track calls.' +sidebarTitle: 'Segment' +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; + +The Segment connector sends selected Novu webhook events to Segment's HTTP API. Its transformation runs once per event and can map a Novu message object to a Segment Track call. + + + +This outbound connector sends Novu events to Segment. To send Segment events into Novu and trigger workflows, use the separate [inbound Segment guide](/guides/analytics/segment). + +## Prerequisites + +- A Segment source that accepts HTTP API calls +- The source **Write Key** +- Permission to manage webhook endpoints in the Novu environment +- Novu event types whose payloads contain the fields your Segment call requires + +## Configuration + +| Dashboard field | Required | Description | +| --- | --- | --- | +| **Write key** | Yes | Segment source Write Key used by the connector. | +| **Transformation** | Yes | Per-message JavaScript that sets the Segment API URL and request payload. | + +## Transformation contract + +Unlike batched storage and messaging connectors, Segment uses `handler(webhook)` for one webhook at a time. The input and output object contains: + +| Property | Description | +| --- | --- | +| `webhook.eventType` | Selected Novu webhook event type. | +| `webhook.payload` | Novu webhook body. Resource data is under `webhook.payload.data.object`. | +| `webhook.url` | Destination URL. Set this to the required Segment API endpoint. | +| `webhook.method` | HTTP method. Supported transformation values are `POST`, `PUT`, and `PATCH`. | +| `webhook.cancel` | Set to `true` when the event should not be sent. | + +The webhook body wraps the resource in an envelope: `webhook.payload.object` is the resource type as a string, such as `"message"`, and the resource itself is under `webhook.payload.data.object`. For message events, `subscriberId` and related message fields are therefore at `webhook.payload.data.object`. + +This example maps message events to Segment Track: + +```js +function handler(webhook) { + const message = webhook.payload.data?.object ?? {}; + + if (!message.subscriberId) { + webhook.cancel = true; + + return webhook; + } + + webhook.url = "https://api.segment.io/v1/track"; + webhook.payload = { + userId: message.subscriberId, + event: webhook.eventType, + properties: { + novu: message, + }, + }; + + return webhook; +} +``` + +Segment rejects a Track call that has neither `userId` nor `anonymousId`, so the example cancels the delivery instead of sending an invalid call when an event carries no subscriber. Cancelled events are not retried. + +The resulting Track payload has this shape: + +```json +{ + "userId": "subscriber-123", + "event": "message.sent", + "properties": { + "novu": { + "subscriberId": "subscriber-123", + "channel": "email" + } + } +} +``` + +Subscribe this example only to message events that include `data.object.subscriberId`. For workflow events or other shapes, inspect the [event type](/platform/developer/webhooks/event-types) payload and map a valid Segment `userId` or `anonymousId`. + +## Configure in the Dashboard + + + +## Prepare the Segment source + +Create or select a Segment source that supports the HTTP Tracking API. Copy its **Write Key**. + + + + + +## Enter connection details + +Enter the **Write key**. + + + +## Configure the transformation + +Map each subscribed event shape, keep `handler(webhook)`, and return the webhook object. + + + +## Select event types + +Choose the [event types](/platform/developer/webhooks/event-types) to send. Add explicit transformation handling for every selected type. + + + +## Test the endpoint + +Create the endpoint. In **Testing**, send an example for each selected event type. Confirm success in **Logs**, then verify the call and payload in the Segment source debugger. + + + +## Verify delivery + +Open the Segment source debugger and confirm the Track call has the expected `userId`, event name, and properties. Compare it with the transformed request in the endpoint's **Logs** tab. + +## Troubleshooting + +- **Segment rejects authentication**: Confirm the Write Key belongs to the intended source and environment. +- **`userId` is missing**: Read it from `webhook.payload.data.object.subscriberId` for supported message events. Inspect the test payload before mapping other event types. +- **Track properties are empty**: Preserve the original payload before assigning a new value to `webhook.payload`. +- **Only some events reach Segment**: Confirm every required event type is selected and handled by the transformation. +- **Events appear in the wrong direction**: This connector is Novu to Segment. Use the [Segment Destination Functions guide](/guides/analytics/segment) for Segment to Novu. + + + +## Official references + +- [Svix Segment connector](https://docs.svix.com/connectors#segment) +- [Segment HTTP Track API](https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#track) diff --git a/docs/guides/webhook-connectors/snowflake.mdx b/docs/guides/webhook-connectors/snowflake.mdx new file mode 100644 index 00000000000..55a62cd5240 --- /dev/null +++ b/docs/guides/webhook-connectors/snowflake.mdx @@ -0,0 +1,180 @@ +--- +title: 'Load webhook events into Snowflake' +description: "Load Novu webhook events into a Snowflake table with the Snowflake connector, including key-pair authentication, the default three column schema, and column-oriented bindings." +sidebarTitle: Snowflake +--- + +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; +import WebhookConnectorAddEndpoint from "/snippets/webhook-connector-add-endpoint.mdx"; +import WebhookConnectorRelated from "/snippets/webhook-connector-related.mdx"; +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +The Snowflake connector inserts Novu webhook events into a Snowflake table. Each batch of webhooks becomes a single parameterized `INSERT` statement. Use it when notification data belongs in your Snowflake warehouse alongside other analytics sources. + +Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). This connector maps to the Svix [Snowflake advanced endpoint](https://docs.svix.com/advanced-endpoints/snowflake). + + + +## Prerequisites + +- A Snowflake account and its account identifier in `organization-account` form, for example `ab12345-xs67890`. +- An RSA key pair, with the public key assigned to a Snowflake user. This connector authenticates with key-pair JWT authentication, not a password. See [key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth). +- The destination table created before you enable the endpoint. The connector does not create or alter tables. + +## Configuration + +| Field | Required | Description | +| --- | --- | --- | +| Account identifier | Yes | Account identifier in `organization-account` form, for example `ab12345-xs67890`. | +| User ID | Yes | The Snowflake user the public key is assigned to. | +| Private key | Yes | The PEM encoded private key that matches the assigned public key. | +| Database name | Only without a transformation | Database that receives the rows. | +| Schema name | Only without a transformation | Schema that contains the table. | +| Table name | Only without a transformation | Table that receives the rows. | +| Transformation | No | JavaScript that returns the SQL statement and bindings to run. | + +When you set a transformation, the statement names the target table directly, so database, schema, and table become optional. + +## Authentication and permissions + +Generate an RSA key pair, assign the public key to the Snowflake user, and paste the complete matching private key into the connector. + +Svix does not publish a required privilege list. Scope the user with Snowflake [`GRANT`](https://docs.snowflake.com/en/sql-reference/sql/grant-privilege) so it can insert into one table and nothing else: + +```sql +GRANT USAGE ON DATABASE my_database TO ROLE novu_writer; +GRANT USAGE ON SCHEMA my_database.my_schema TO ROLE novu_writer; +GRANT INSERT ON TABLE my_database.my_schema.novu_events TO ROLE novu_writer; +GRANT USAGE ON WAREHOUSE novu_wh TO ROLE novu_writer; +GRANT ROLE novu_writer TO USER novu_user; +``` + +The connector configuration has no warehouse field. Set a default warehouse and default role on the Snowflake user with the `DEFAULT_WAREHOUSE` and `DEFAULT_ROLE` properties of [`CREATE USER`](https://docs.snowflake.com/en/sql-reference/sql/create-user) so statements have compute to run on. + +## Default destination behavior + +Without a transformation, Svix inserts into the table identified by the database, schema, and table fields using three columns. It generates a unique `id`, sets `created_at` to the insert time, and writes the raw payload to `payload`: + +```sql +CREATE TABLE my_database.my_schema.my_table ( + id TEXT, + created_at TIMESTAMP, + payload TEXT +); +``` + +The table must exist before you enable the endpoint. + + +## Create an analytics table + +```sql +CREATE TABLE my_database.my_schema.novu_events ( + event_type TEXT, + subscriber_id TEXT, + channel TEXT, + payload TEXT, + received_at TIMESTAMP_LTZ DEFAULT CURRENT_TIMESTAMP() +); +``` + +## Transformation contract + +The transformation shapes each batch into one SQL statement: + + +- Output: an object with `statement` and `bindings`. +- `statement` is the SQL to execute, referencing bindings by name, for example `:subscriber_id`. +- `bindings` is column-oriented. It is an object keyed by binding name, and each binding has a Snowflake `type` and a `value` array holding one entry per row in the batch. All arrays must be the same length. +- Send every value as a string. For allowed types, see [using bind variables in a statement](https://docs.snowflake.com/en/developer-guide/sql-api/submitting-requests#using-bind-variables-in-a-statement). + +Novu-oriented example that matches the analytics table above and reads message fields from `event.payload.data.object`: + +```javascript +function handler(input) { + const bindings = { + event_type: { type: "TEXT", value: [] }, + subscriber_id: { type: "TEXT", value: [] }, + channel: { type: "TEXT", value: [] }, + payload: { type: "TEXT", value: [] }, + }; + + input.events.forEach((event) => { + const message = event.payload.data?.object ?? {}; + + bindings.event_type.value.push(String(event.eventType)); + bindings.subscriber_id.value.push(String(message.subscriberId ?? "")); + bindings.channel.value.push(String(message.channel ?? "")); + bindings.payload.value.push(JSON.stringify(event.payload)); + }); + + return { + bindings, + statement: + "INSERT INTO MY_DATABASE.MY_SCHEMA.NOVU_EVENTS (event_type, subscriber_id, channel, payload) VALUES (:event_type, :subscriber_id, :channel, :payload);", + }; +} +``` + +Use the `FIXED` type for numeric columns, and still push the value as a string. + +## Configure in the Dashboard + + + + + + +## Enter connection details + +Enter the account identifier, user ID, and private key. Fill in database, schema, and table if you are relying on the default insert behavior. + + + +## Configure the transformation + +Paste a transformation whose statement names your table and whose bindings match its columns. + + + +## Select event types + +Subscribe to the [event types](/platform/developer/webhooks/event-types) you want to store. Every subscribed type runs through the same statement, so confirm each binding array is filled for all of them. + + + +## Test the endpoint + +Click **Create**, open the endpoint, go to the **Testing** tab, and use **Send Example** for each subscribed event type. Confirm success in the **Logs** tab. + + + + +## Verify delivery + +```sql +SELECT event_type, subscriber_id, channel, received_at +FROM my_database.my_schema.novu_events +ORDER BY received_at DESC +LIMIT 10; +``` + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| JWT or authentication errors | The private key does not match the public key assigned to the user, the account identifier is wrong, or the key was pasted with missing lines. Key-pair authentication is required, so a password will not work. | +| No active warehouse errors | The user has no default warehouse, or no `USAGE` grant on it. The connector configuration has no warehouse field. | +| Insufficient privileges | The role is missing `USAGE` on the database or schema, or `INSERT` on the table. | +| Binding count errors | The `value` arrays have different lengths. Push one entry per event to every binding, including when a field is missing. | +| Type or conversion errors | A value was pushed as a number or object. Send strings, and use `FIXED` for numeric columns. | +| Table not found | Database, schema, and table fields are only used without a transformation. With a transformation, the fully qualified name in the statement is what matters. | +| Statement runs but no rows appear | The statement targets a different database or schema than the one you are querying. Check the fully qualified name in the transformation. | + + + +## Official references + +- [Svix Snowflake endpoint](https://docs.svix.com/advanced-endpoints/snowflake) +- [Snowflake key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth) +- [Snowflake SQL API bind variables](https://docs.snowflake.com/en/developer-guide/sql-api/submitting-requests#using-bind-variables-in-a-statement) diff --git a/docs/platform/developer/webhooks/connectors.mdx b/docs/platform/developer/webhooks/connectors.mdx index 9316d6ef6ae..dbe505bea1c 100644 --- a/docs/platform/developer/webhooks/connectors.mdx +++ b/docs/platform/developer/webhooks/connectors.mdx @@ -1,856 +1,102 @@ --- title: 'Webhook connectors' -description: "Send Novu webhook events directly to data warehouses, object storage, messaging services, Segment, and OpenTelemetry collectors." +description: 'Send Novu webhook events to data warehouses, object storage, messaging services, analytics tools, and observability collectors.' --- -In addition to standard HTTP webhook endpoints, Novu supports webhook connectors that deliver events directly to third-party services. Use them to route Novu events to data warehouses, object storage, messaging queues, analytics tools, and observability collectors without building a custom receiver. - -Novu webhook delivery is powered by [Svix](https://docs.svix.com/introduction). Connector endpoints use Svix [transformations](https://docs.svix.com/transformations) to shape each event before it is written to your destination. - - - Outbound webhooks feature is only available in the [Team and Enterprise plans](https://novu.co/pricing). - - -## How to add a connector endpoint - - - - Go to the **[Webhooks](https://dashboard.novu.co/webhooks)** page in the Novu dashboard. - - - - In the Novu dashboard, open **Settings** > **Webhooks**, then select **Endpoints**. - - - - On the **Webhooks** page, click **Add Endpoint**. - - - - Search for or select the connector you want to use from the integrations list. - ![Webhook integrations list](/images/developer-tools/webhook-integrations.png) - - - - Add a label that helps you identify this connector later. - - - - Configure the connector-specific connection settings described in the sections below. - - - - For **data warehouse connectors** (ClickHouse, Snowflake, Redshift, BigQuery, Postgres): define the **table schema** and write a **transformation** that maps Novu webhook payloads to rows matching that schema. See [Data warehouse connectors](#data-warehouse-connectors). - - - - For **object storage connectors** (Amazon S3, Azure Blob Storage, Google Cloud Storage): review the **transformation** that controls object key and file format. See [Object storage connectors](#object-storage-connectors). - - - - For **message connectors** (Amazon SQS, Amazon SNS, Amazon EventBridge, RabbitMQ, Google Cloud PubSub): review and customize the **transformation** that shapes the message body. See [Message connectors](#message-connectors). - - - - For **Segment**, **OpenTelemetry Collector**, **Polling Endpoint**, or **FIFO Endpoint**, follow the connector-specific sections below. - - - - Select the [event types](/platform/developer/webhooks/event-types) you want this endpoint to subscribe to. - - - - Review the configuration, then click **Create**. - - - - Open the endpoint, go to the **Testing** tab, and **Send Example** for each subscribed event type. Confirm delivery succeeds in **Logs**, then verify the row, object, or message appears in your destination. - - - -After the endpoint is created, Novu delivers matching events to your configured destination. You can monitor delivery attempts, retry failed messages, and test the endpoint from the dashboard. - -## Available connectors - -Novu supports the following webhook connectors. The list matches the **Add Endpoint** integrations grid in the dashboard: - -| Connector | Type | Description | -| --- | --- | --- | -| [Polling Endpoint](#polling-endpoint) | HTTP | Receive events by polling an endpoint. | -| [FIFO Endpoint](#fifo-endpoint) | HTTP | Receive events over HTTP in FIFO order. | -| [Amazon S3](#amazon-s3) | Object storage | Store events in an Amazon S3 bucket. | -| [Azure Blob Storage](#azure-blob-storage) | Object storage | Store events in an Azure Blob Storage container. | -| [Google Cloud Storage](#google-cloud-storage) | Object storage | Store events in a Google Cloud Storage bucket. | -| [Google Cloud PubSub](#google-cloud-pubsub) | Message | Send events to a Google Cloud Pub/Sub topic. | -| [OpenTelemetry Collector](#opentelemetry-collector) | Observability | Stream events to an OpenTelemetry Collector. | -| [Amazon SQS](#amazon-sqs) | Message | Send events to an Amazon SQS queue. | -| [Amazon SNS](#amazon-sns) | Message | Send events to an Amazon SNS topic. | -| [Amazon EventBridge](#amazon-eventbridge) | Message | Send events to an AWS EventBridge event bus. | -| [Postgres](#postgres) | Data warehouse | Insert events as rows into a Postgres table. | -| [Google BigQuery](#google-bigquery) | Data warehouse | Store events in a Google Cloud BigQuery table. | -| [ClickHouse](#clickhouse) | Data warehouse | Store events in a ClickHouse table. | -| [Snowflake](#snowflake) | Data warehouse | Store events in a Snowflake table. | -| [RabbitMQ](#rabbitmq) | Message | Send events to a RabbitMQ exchange. | -| [Amazon Redshift](#amazon-redshift) | Data warehouse | Store events in an Amazon Redshift table. | -| [Segment](#segment) | Analytics | Send events to Segment. | - -## Transformations - -Every connector endpoint runs a JavaScript **transformation** before delivery. The transformation converts the raw Novu webhook into the format your destination expects. - -In the Novu dashboard, configure the transformation when creating or editing a connector endpoint. Each connector type ships with a default template - use it as your starting point. - -### How to write a transformation - -Svix expects a function named `handler`. For connector endpoints, the handler receives a `webhook` object with: - -| Property | Description | -| --- | --- | -| `webhook.eventType` | The Novu event type (for example, `message.sent`). | -| `webhook.payload` | The webhook JSON body. Shape depends on the event type - see [event types](/platform/developer/webhooks/event-types). | -| `webhook.method` | HTTP method. Usually `"POST"`. | -| `webhook.url` | Destination URL. Generally leave unchanged for connectors. | -| `webhook.cancel` | Set to `true` to skip delivery for a given event. Defaults to `false`. | - -The handler must return the `webhook` object after modifying `webhook.payload` (and optionally other allowed properties). Test your code in the dashboard against a sample payload before going live. - - - Handle every [event type](/platform/developer/webhooks/event-types) your endpoint subscribes to. Use `switch (webhook.eventType)` or equivalent logic so each event maps to a valid destination record. - - -## Data warehouse connectors - -Data warehouse connectors (ClickHouse, Snowflake, Redshift, BigQuery, Postgres) insert one row per delivered webhook. Credentials and table location alone are not enough - you must also configure: - -1. **Table schema** - Column names and types that match your destination table. -2. **Transformation** - JavaScript that maps each webhook `eventType` and `payload` into a row compatible with that schema. - -### Recommended setup flow - - - - Create a table with columns for the webhook fields you want to store. - - - - Ensure the database user Novu connects with can insert rows into the destination table. - - - - In Novu, add the connector endpoint and fill in connection settings (URL, credentials, database, and table). - - - - Map Novu payload fields to your destination table columns. - - - - Map Novu payloads into your table schema. Start from the dashboard template and adjust as needed. - - - - **Subscribe** to the event types you want to store. - - - - **Test** with **Send Example**, confirm success in **Logs**, then query the table to verify rows were inserted. - - - -### Example transformation - -This example maps a `message.sent` event into a flat row. Adjust field names to match your table schema and add cases for every event type you subscribe to. - -```js -function handler(webhook) { - switch (webhook.eventType) { - case "message.sent": - webhook.payload = { - event_type: webhook.eventType, - message_id: webhook.payload.id, - subscriber_id: webhook.payload.subscriberId, - channel: webhook.payload.channel, - created_at: webhook.payload.createdAt, - raw_payload: JSON.stringify(webhook.payload), - }; - break; - // Add a case for each subscribed event type - } - - return webhook; -} -``` - -If deliveries succeed in Novu but no rows appear in your warehouse, the transformation output likely does not match your table schema. Compare the transformed payload in the **Logs** tab with your table definition. - -## Message connectors - -Message connectors (Amazon SQS, Amazon SNS, Amazon EventBridge, RabbitMQ, Google Cloud PubSub) publish each webhook as a queue message, topic notification, or bus event. Use the transformation to shape the JSON body your consumers receive. - -The default template typically forwards the webhook payload. Customize it if downstream workers expect a specific structure (for example, flattening nested fields or adding metadata). - -```js -function handler(webhook) { - webhook.payload = { - eventType: webhook.eventType, - data: webhook.payload, - }; - - return webhook; -} -``` - -After creating the endpoint, send a test event and confirm the message appears in your SQS queue, SNS topic, EventBridge bus, RabbitMQ exchange, or Pub/Sub topic. - -## Object storage connectors - -Object storage connectors (Amazon S3, Azure Blob Storage, Google Cloud Storage) write webhook batches as objects in your bucket or container. Use the transformation to control: - -- **Object key** - the file name prefix. A timestamp is suffixed so each batch is unique. -- **Format** - `jsonl` (default), `json`, or `raw` (exact file contents as a string). - -```js -function handler(webhook) { - webhook.payload = { - config: { - format: "jsonl", - key: "novu-events", - }, - data: [ - { - eventType: webhook.eventType, - payload: webhook.payload, - }, - ], - }; - - return webhook; -} -``` - -Start from the dashboard template for your storage provider. After creating the endpoint, send a test event and confirm a new object appears in the bucket or container. - -## Analytics connectors - -The [Segment](#segment) connector forwards Novu webhook events to Segment. Provide a Segment **Write Key** in the dashboard, then map each subscribed [event type](/platform/developer/webhooks/event-types) to a Segment Track (or Identify) payload in the transformation. - -This is outbound delivery from Novu to Segment. To send Segment events *into* Novu as workflow triggers, see the [Segment destination function guide](/guides/analytics/segment). - -## ClickHouse - -The ClickHouse connector stores Novu webhook events directly in a ClickHouse table. Use it when you want to analyze notification events in ClickHouse without building a custom ingestion pipeline. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| URL | Yes | The ClickHouse server URL (for example, `https://clickhouse.example.com:8443`). | -| Username | Yes | The username used to authenticate with ClickHouse. | -| Password | Yes | The password for the ClickHouse user. | -| Table name | Yes | The name of the table where events are stored. | -| Database | No | The ClickHouse database name. If omitted, the default database is used. | -| Table schema | Yes | Column definitions that match your ClickHouse table. | -| Transformation | Yes | JavaScript that maps webhook payloads to rows matching the table schema. | - -### Setup - - - - Define columns for the webhook event fields you want to store. - - - - Ensure the configured user has permission to insert rows into the target table. - - - - In the Novu dashboard, select **ClickHouse** when adding a webhook endpoint. - - - - Enter credentials, define the table schema, and review the transformation. - - - - Select event types, create the endpoint, and verify rows with **Send Example**. - - - -## Snowflake - -The Snowflake connector stores Novu webhook events directly in a Snowflake table. Use it when you want notification data available in your Snowflake data warehouse for reporting, analytics, or downstream pipelines. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Account identifier | Yes | Your Snowflake account identifier. | -| User ID | Yes | The Snowflake user used to authenticate. | -| Private key | Yes | The private key for key-pair authentication. | -| Database name | No | The Snowflake database where events are stored. | -| Schema name | No | The schema containing the target table. | -| Table name | No | The table where events are stored. | -| Table schema | Yes | Column definitions that match your Snowflake table. | -| Transformation | Yes | JavaScript that maps webhook payloads to rows matching the table schema. | - -### Setup - - - - Define columns for the webhook event fields you want to store. - - - - Configure a Snowflake user with key-pair authentication and insert permissions on the target table. - - - - In the Novu dashboard, select **Snowflake** when adding a webhook endpoint. - - - - Enter credentials, define the table schema, and review the transformation. - - - - Select event types, create the endpoint, and verify rows with **Send Example**. - - - -## Amazon Redshift - -The Amazon Redshift connector stores Novu webhook events directly in a Redshift table. Use it when you want notification events available in your Redshift data warehouse for analytics and reporting. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Region | Yes | The AWS region where your Redshift cluster or workgroup is located. | -| Access key ID | Yes | The AWS access key ID with permission to write to Redshift. | -| Secret access key | Yes | The AWS secret access key for the IAM user. | -| Cluster identifier | No | The Redshift cluster identifier. Required for provisioned clusters. | -| Workgroup name | No | The Redshift Serverless workgroup name. Required for serverless deployments. | -| Database user | No | The database user for provisioned clusters. | -| Database name | No | The Redshift database where events are stored. | -| Schema name | No | The schema containing the target table. | -| Table name | No | The table where events are stored. | -| Table schema | Yes | Column definitions that match your Redshift table. | -| Transformation | Yes | JavaScript that maps webhook payloads to rows matching the table schema. | - - - For provisioned Redshift clusters, set **Cluster identifier** and **Database user**. For Redshift Serverless, set **Workgroup name** instead. - - -### Setup - - - - Define columns for the webhook event fields you want to store. - - - - Grant only the permissions this connector needs. - - - - In the Novu dashboard, select **Redshift** when adding a webhook endpoint. - - - - Enter credentials, define the table schema, and review the transformation. - - - - Select event types, create the endpoint, and verify rows with **Send Example**. - - - -## Amazon SQS - -The Amazon SQS connector sends Novu webhook events to an Amazon SQS queue. Use it when you want to process notification events asynchronously through your existing AWS messaging infrastructure. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Queue URL | Yes | The URL of the target SQS queue. | -| Region | Yes | The AWS region where the queue is located. | -| Access key ID | Yes | The AWS access key ID with permission to send messages to the queue. | -| Secret access key | Yes | The AWS secret access key for the IAM user. | -| Endpoint URL | No | A custom endpoint URL. Use this when connecting to LocalStack or other SQS-compatible services. | -| Transformation | Yes | JavaScript that shapes the message body sent to the queue. | - -### Setup - - - - Use this queue as the destination for Novu webhook events. - - - - Grant only the permissions this connector needs. - - - - In the Novu dashboard, select **SQS** when adding a webhook endpoint. - - - - Enter your connection credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify messages with **Send Example**. - - - -## Amazon SNS - -The Amazon SNS connector publishes Novu webhook events to an Amazon SNS topic. Use it when you want to fan out notification events to multiple subscribers through SNS. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Topic ARN | Yes | The ARN of the target SNS topic. | -| Region | Yes | The AWS region where the topic is located. | -| Access key ID | Yes | The AWS access key ID with permission to publish to the topic. | -| Secret access key | Yes | The AWS secret access key for the IAM user. | -| Endpoint URL | No | A custom endpoint URL. Use this when connecting to LocalStack or other SNS-compatible services. | -| Transformation | Yes | JavaScript that shapes the notification body published to the topic. | - -### Setup - - - - Use this topic as the destination for Novu webhook events. - - - - Grant only the permissions this connector needs. - - - - In the Novu dashboard, select **SNS** when adding a webhook endpoint. - - - - Enter your connection credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify messages with **Send Example**. - - - -## Google BigQuery - -The Google BigQuery connector stores Novu webhook events in a BigQuery table. Use it when you want notification data available in BigQuery for analytics without a custom ingestion pipeline. - -Without a custom transformation, each webhook is inserted using two columns: `id` and `payload`. Create the table before enabling the endpoint. If you define a table schema and transformation in Novu, the object keys in the transformed row must match your column names. - -```sql -CREATE TABLE `my-gcp-project.my_dataset.events` ( - id STRING, - payload STRING -); -``` - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Project ID | Yes | The GCP project that owns the dataset. | -| Dataset ID | Yes | The BigQuery dataset that contains the table. | -| Table ID | Yes | The table that receives the rows. | -| Credentials | Yes | Google Cloud service account credentials JSON, provided as a string. The service account needs permission to insert rows into the table. | -| Table schema | Yes | Column definitions that match your BigQuery table. | -| Transformation | Yes | JavaScript that maps webhook payloads to rows matching the table schema. | - -### Setup - - - - Create the table before enabling the endpoint. The default mapping uses `id` and `payload` columns. - - - - Grant insert permission on the table and download the JSON key. - - - - In the Novu dashboard, select **BigQuery** when adding a webhook endpoint. - - - - Enter credentials, define the table schema, and review the transformation. - - - - Select event types, create the endpoint, and verify rows with **Send Example**. - - - -## Postgres - -The Postgres connector inserts Novu webhook events as rows in a Postgres table. Use it when you want a durable operational store of notification events in your own database. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Connection settings | Yes | Host, port, database, username, and password shown in the dashboard form. | -| Table name | Yes | The table where events are stored. | -| Table schema | Yes | Column definitions that match your Postgres table. | -| Transformation | Yes | JavaScript that maps webhook payloads to rows matching the table schema. | - -### Setup - - - - Define columns for the webhook event fields you want to store. - - - - Grant `INSERT` on the table to the user Novu connects with. - - - - In the Novu dashboard, select **Postgres** when adding a webhook endpoint. - - - - Enter credentials, define the table schema, and review the transformation. - - - - Select event types, create the endpoint, and verify rows with **Send Example**. - - - -## Amazon EventBridge - -The Amazon EventBridge connector publishes Novu webhook events to an EventBridge event bus. Each webhook is sent as a separate entry. The event `source` is set by the delivery system; `detail-type` comes from the **Detail type** field; `detail` is the transformed message body. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Event bus name | Yes | The name or ARN of the event bus that receives the events. | -| Detail type | No | Free-form string (max 128 characters) used as `detail-type`. Defaults to `application/json`. | -| Region | Yes | The AWS region where the event bus is located. | -| Access key ID | Yes | The AWS access key ID with permission to put events on the bus. | -| Secret access key | Yes | The AWS secret access key for the IAM user. | -| Transformation | Yes | JavaScript that shapes the `detail` body of each EventBridge event. | - -### Setup - - - - Create a bus (or use the default bus) and rules that match the source and detail type. - - - - Grant `events:PutEvents` on the bus. - - - - In the Novu dashboard, select **EventBridge** when adding a webhook endpoint. - - - - Enter your connection credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify events with **Send Example**. - - - -## RabbitMQ - -The RabbitMQ connector publishes Novu webhook events to a RabbitMQ exchange using a routing key. Each webhook is published as a separate message. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| URI | Yes | The AMQP connection URI (for example, `amqp://user:password@rabbitmq.example.com:5672/my-vhost`). | -| Routing key | Yes | The routing key each message is published with. | -| Transformation | Yes | JavaScript that shapes the message body published to RabbitMQ. | - -### Setup - - - - Bind a queue to the routing key you will use. - - - - In the Novu dashboard, select **RabbitMQ** when adding a webhook endpoint. - - - - Enter the AMQP URI and routing key, then review the transformation. - - - - Select event types, create the endpoint, and verify messages with **Send Example**. - - - -## Amazon S3 - -The Amazon S3 connector stores Novu webhook batches as objects in an S3 bucket. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Bucket | Yes | The name of the S3 bucket. | -| Region | Yes | The AWS region where the bucket is located. | -| Access key ID | Yes | The AWS access key ID with permission to put objects in the bucket. | -| Secret access key | Yes | The AWS secret access key for the IAM user. | -| Transformation | Yes | JavaScript that sets the object key, format (`jsonl`, `json`, or `raw`), and contents. | - -### Setup - - - - Use this bucket as the destination for Novu webhook event objects. - - - - Grant `s3:PutObject` on the bucket. - - - - In the Novu dashboard, select **Amazon S3** when adding a webhook endpoint. - - - - Enter your connection credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify objects with **Send Example**. - - - -## Azure Blob Storage - -The Azure Blob Storage connector stores Novu webhook batches as blobs in a storage container. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Container | Yes | The Azure Blob Storage container name. | -| Account | Yes | The Azure storage account name. | -| Access key | Yes | The storage account access key. | -| Transformation | Yes | JavaScript that sets the blob key, format, and contents. | - -### Setup - - - - Use this container as the destination for Novu webhook event blobs. - - - - In the Novu dashboard, select **Azure Blob Storage** when adding a webhook endpoint. - - - - Enter your connection credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify blobs with **Send Example**. - - - -## Google Cloud Storage - -The Google Cloud Storage connector stores Novu webhook batches as objects in a GCS bucket. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Bucket | Yes | The GCS bucket name. | -| Credentials | Yes | Google Cloud service account credentials JSON, provided as a string. The service account needs permission to write objects to the bucket. | -| Transformation | Yes | JavaScript that sets the object key, format, and contents. | - -### Setup - - - - Create a service account with object-create permission on the bucket. - - - - In the Novu dashboard, select **Google Cloud Storage** when adding a webhook endpoint. - - - - Enter credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify objects with **Send Example**. - - - -## Google Cloud PubSub - -The Google Cloud PubSub connector publishes Novu webhook events to a Pub/Sub topic. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Project ID | Yes | The GCP project that owns the topic. | -| Topic | Yes | The Pub/Sub topic that receives events. | -| Credentials | Yes | Google Cloud service account credentials JSON, provided as a string. The service account needs permission to publish to the topic. | -| Transformation | Yes | JavaScript that shapes the message body published to the topic. | - -### Setup - - - - Create a subscription as well if you want to inspect messages. - - - - Grant `pubsub.topics.publish` permission. - - - - In the Novu dashboard, select **Google Cloud Pub/Sub** when adding a webhook endpoint. - - - - Enter credentials and review the transformation before saving. - - - - Select event types, create the endpoint, and verify messages with **Send Example**. - - - -## Segment - -The Segment connector sends Novu webhook events to Segment's HTTP API using your **Write Key**. Use the transformation to format each event for [Segment Track](https://segment.com/docs/connections/sources/catalog/libraries/server/http-api/#track) (or another Segment API you need). - -```js -function handler(webhook) { - webhook.url = "https://api.segment.io/v1/track"; - webhook.payload = { - userId: webhook.payload.subscriberId, - event: webhook.eventType, - properties: webhook.payload, - }; - - return webhook; -} -``` - -Adjust `userId` and `properties` to match the [event types](/platform/developer/webhooks/event-types) you subscribe to. Workflow events may not include `subscriberId`. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| Write key | Yes | The Segment source write key. | -| Transformation | Yes | JavaScript that maps Novu payloads to Segment API requests. | - -### Setup - - - - Copy the source write key. - - - - In the Novu dashboard, select **Segment** when adding a webhook endpoint. - - - - Enter the write key and review the transformation. - - - - Select event types, create the endpoint, and verify events in the Segment debugger. - - - -## OpenTelemetry Collector - -The OpenTelemetry Collector connector streams Novu webhook events as OpenTelemetry spans to a collector over OTLP. Use it to view notification activity in Datadog, Grafana, and other observability platforms that ingest OTLP traces. - -The collector URL is typically your provider's `OTEL_EXPORTER_OTLP_ENDPOINT` with `/v1/traces` appended. You can add custom headers (for example, an API key) the same way as on a standard webhook endpoint. - -The dashboard ships a default transformation that maps each webhook to a span. Override `startTime` and `endTime` from payload timestamps when you have them, and set `traceIdKey` if you need multiple events grouped into one trace. - -### Configuration - -| Field | Required | Description | -| --- | --- | --- | -| URL | Yes | The OTLP HTTP traces endpoint (for example, `https://otlp.example.com/v1/traces`). | -| Headers | No | Custom headers required by your observability vendor. | -| Transformation | Yes | JavaScript that maps webhooks to OpenTelemetry spans. | - -### Setup - - - - Copy the URL and any required headers from your observability vendor. - - - - In the Novu dashboard, select **OpenTelemetry Collector** when adding a webhook endpoint. - - - - Enter the URL, optional headers, and review the transformation. - - - - Select event types, create the endpoint, and verify spans in your observability platform. - - - -## Polling Endpoint - -A polling endpoint lets you pull Novu webhook events on a schedule instead of exposing a public HTTP receiver. Use it for local testing, private networks, or batching events at the end of a day. - -After you create the endpoint, the dashboard provides a unique polling URL and API key. Poll with a stable **consumer ID** so each worker tracks its own offset. After processing a batch, commit the last message offset so the next poll continues from there. - -Polling endpoints can run alongside push-based webhook and connector endpoints. - -## FIFO Endpoint - -A FIFO endpoint delivers events over HTTP in strict first-in, first-out order. Regular webhook endpoints deliver independently and only guarantee order on a best-effort basis. - -Strict ordering limits throughput: each call waits for a successful acknowledgement of the previous batch. FIFO endpoints therefore deliver webhooks in configurable batch sizes. Use a FIFO endpoint when consumers must process events in the order they were produced (for example, sequential status transitions). - -## Monitoring and troubleshooting - -Connector endpoints support the same delivery monitoring, retry, and recovery features as standard webhook endpoints. From the endpoint details page in the Novu dashboard, you can: - -- View delivery attempts and failure reasons in the **Logs** tab. -- Inspect the transformed payload sent to your destination. -- Resend individual failed events. -- Recover or replay failed messages from a selected time window. - -If deliveries fail consistently, check the following: - -| Symptom | What to check | -| --- | --- | -| Authentication errors | Connector credentials, IAM permissions, or database user grants. | -| Schema or type errors | Table schema matches your destination table; transformation output uses the correct column names and types. | -| Missing rows or messages | Delivery shows success in **Logs** but destination is empty - transformation may not match the expected insert or message format. | -| Unhandled event types | Transformation includes a branch for every subscribed [event type](/platform/developer/webhooks/event-types). | - -If the destination service is unreachable from Novu's webhook infrastructure, deliveries will continue to retry according to the [retry schedule](/platform/developer/webhooks#recovery-and-resending-failed-messages) before the endpoint is disabled. +import WebhookConnectorsPlanNote from "/snippets/webhook-connectors-plan-note.mdx"; + +Webhook connectors deliver Novu events directly to services that would otherwise require a custom HTTP receiver. Novu uses [Svix](https://docs.svix.com/introduction) to manage connector delivery, transformations, retries, and endpoint logs. + + + +Each connector has a step-by-step setup guide covering prerequisites, configuration fields, the transformation contract, verification, and troubleshooting. Select a destination below to open its guide. + +## Data warehouses + +Store notification events as queryable rows. Create the destination table before you enable the endpoint, because connectors do not create or alter tables. + +Before you pick a warehouse, read [Plan your export](/guides/webhook-connectors/data-warehouse-sync) for the event types to subscribe to and the schema decisions that apply to every warehouse. + + + + Insert Novu events into a ClickHouse table with `JSONEachRow` or a batch transformation. + + + Insert notification events into BigQuery for delivery and engagement analysis. + + + Write webhook batches to Snowflake with key-pair authentication. + + + Write webhook batches through the Redshift Data API. + + + Insert Novu events as rows in a Postgres table. + + + +## Object storage + +Write each webhook batch as a timestamped object. Choose JSON Lines, JSON, or raw output in the connector transformation. + + + + Store webhook batches as objects in an Amazon S3 bucket. + + + Store webhook batches as blobs in an Azure Storage container. + + + Store webhook batches as objects in a Google Cloud Storage bucket. + + + +## Messaging + +Publish Novu events to a queue, topic, exchange, or event bus for asynchronous processing. + + + + Send Novu events to an Amazon SQS queue. + + + Publish Novu events to an Amazon SNS topic. + + + Publish Novu events to an Amazon EventBridge event bus. + + + Publish Novu events to RabbitMQ with a routing key. + + + Publish Novu events to a Google Cloud Pub/Sub topic. + + + +## Analytics and observability + +Send Novu events to analytics and tracing systems without operating a receiver service. + + + + Map Novu events to Segment Track calls. + + + Convert Novu events into spans and send them to an OTLP traces endpoint. + + + +## HTTP delivery + +Use polling when a consumer cannot accept inbound HTTP requests. Use FIFO delivery when the consumer must process webhook batches in strict order. + + + + Pull events with an endpoint-scoped token and commit offsets after processing. + + + Deliver webhook batches over HTTP in strict first-in, first-out order. + + + +For a standard push endpoint, see [Webhooks](/platform/developer/webhooks/webhooks). For payload availability, see [Event types](/platform/developer/webhooks/event-types). diff --git a/docs/platform/developer/webhooks/event-types.mdx b/docs/platform/developer/webhooks/event-types.mdx index 9204b45f809..8232c920629 100644 --- a/docs/platform/developer/webhooks/event-types.mdx +++ b/docs/platform/developer/webhooks/event-types.mdx @@ -20,40 +20,47 @@ Each event includes detailed information about the affected resource and the cha `email.received` is for **user mail** received on your domain. It is not the same as delivery or engagement events from your outbound email provider - see [Email Activity Tracking](/platform/integrations/email/activity-tracking) for those. -Example payload shape: +Every webhook body uses the same envelope. `type` is the event type, `object` is the resource type as a string, and the resource itself is under `data.object`: ```json { - "object": { - "domain": { - "id": "domain_id", - "name": "mail.yourcompany.com", - "data": {} - }, - "route": { - "address": "support", - "data": { "tenantId": "acme" } - }, - "mail": { - "from": [{ "address": "user@example.com", "name": "Jane" }], - "to": [{ "address": "support@mail.yourcompany.com" }], - "subject": "Re: Your order", - "text": "Thanks, that helps!", - "html": "

Thanks, that helps!

", - "messageId": "", - "inReplyTo": "", - "references": [""], - "headers": {}, - "attachments": [ - { - "filename": "invoice.pdf", - "contentType": "application/pdf", - "size": 24576, - "url": "https://files.example.com/inbound/attachments/invoice.pdf?expires=...", - "expiresAt": "2026-06-30T18:00:00.000Z" - } - ], - "date": "2026-06-30T12:00:00.000Z" + "id": "evt_67f8a1b2c3d4e5f6a7b8c9d0", + "type": "email.received", + "object": "email_inbound", + "timestamp": "2026-06-30T12:00:01.000Z", + "environmentId": "development", + "data": { + "object": { + "domain": { + "id": "domain_id", + "name": "mail.yourcompany.com", + "data": {} + }, + "route": { + "address": "support", + "data": { "tenantId": "acme" } + }, + "mail": { + "from": [{ "address": "user@example.com", "name": "Jane" }], + "to": [{ "address": "support@mail.yourcompany.com" }], + "subject": "Re: Your order", + "text": "Thanks, that helps!", + "html": "

Thanks, that helps!

", + "messageId": "", + "inReplyTo": "", + "references": [""], + "headers": {}, + "attachments": [ + { + "filename": "invoice.pdf", + "contentType": "application/pdf", + "size": 24576, + "url": "https://files.example.com/inbound/attachments/invoice.pdf?expires=...", + "expiresAt": "2026-06-30T18:00:00.000Z" + } + ], + "date": "2026-06-30T12:00:00.000Z" + } } } } diff --git a/docs/platform/developer/webhooks/webhooks.mdx b/docs/platform/developer/webhooks/webhooks.mdx index 42606d493d4..808446316d6 100644 --- a/docs/platform/developer/webhooks/webhooks.mdx +++ b/docs/platform/developer/webhooks/webhooks.mdx @@ -73,7 +73,7 @@ To start listening to messages, you will need to configure your endpoints. 1. Go to the [Webhooks](https://dashboard.novu.co/webhooks) page in the Novu dashboard. 2. Click **Add Endpoint**. 3. Select an integration. Choose **Webhook** for an HTTPS URL, **Polling Endpoint** or **FIFO Endpoint** for alternative HTTP delivery, or a [connector](/platform/developer/webhooks/connectors) such as BigQuery, S3, or Segment. -4. For a **Webhook** endpoint, enter the URL. For a connector, fill in the destination-specific settings, table schema (for warehouses), and transformation. +4. For a **Webhook** endpoint, enter the URL. For a connector, follow its destination-specific setup from the [connectors overview](/platform/developer/webhooks/connectors). 5. Add a description for this webhook endpoint. 6. Select the event types you want to listen to. 7. Optional: add advanced configuration for your endpoint. @@ -172,7 +172,7 @@ We suggest having your endpoint simply receive the message and add it to a queue If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled. To re-enable a disabled endpoint, go to the webhook dashboard, find the endpoint from the list and select "Enable Endpoint". -### Recovering/Resending failed messages +### Recovering and resending failed messages If your service has downtime or if your endpoint was misconfigured, you probably want to recover any messages that failed during the downtime. diff --git a/docs/platform/inbound-email/overview.mdx b/docs/platform/inbound-email/overview.mdx index a92af67bfc5..d136f46c00f 100644 --- a/docs/platform/inbound-email/overview.mdx +++ b/docs/platform/inbound-email/overview.mdx @@ -71,7 +71,7 @@ Set the local-part to `*` to match any address on the domain that does not have ### Route metadata -Each route supports optional JSON **metadata**. Novu includes this metadata in webhook payloads under `route.data`, so you can pass tenant IDs, product slugs, or routing hints without encoding them in the email address. +Each route supports optional JSON **metadata**. Novu includes this metadata in webhook payloads under `data.object.route.data`, so you can pass tenant IDs, product slugs, or routing hints without encoding them in the email address. ## Webhook routes and email.received @@ -83,7 +83,7 @@ The event payload includes normalized mail fields (from, to, subject, text, html ### Attachments -Each item in `object.mail.attachments` is metadata plus a time-limited download URL. Fetch the file with an HTTP `GET` to `url` before `expiresAt`. After that timestamp the link stops working, so copy the bytes into your own storage if you need them later. +Each item in `data.object.mail.attachments` is metadata plus a time-limited download URL. Fetch the file with an HTTP `GET` to `url` before `expiresAt`. After that timestamp the link stops working, so copy the bytes into your own storage if you need them later. | Field | Type | Description | | --- | --- | --- | @@ -94,7 +94,7 @@ Each item in `object.mail.attachments` is metadata plus a time-limited download | `expiresAt` | `string` | ISO-8601 time when `url` stops being valid (six hours after delivery) | ```typescript -for (const attachment of payload.object.mail.attachments ?? []) { +for (const attachment of payload.data?.object?.mail?.attachments ?? []) { const response = await fetch(attachment.url); const bytes = await response.arrayBuffer(); // Persist `bytes` in your storage. Do not keep `attachment.url` as a long-term file location. diff --git a/docs/snippets/object-storage-transformation.mdx b/docs/snippets/object-storage-transformation.mdx new file mode 100644 index 00000000000..d43959906e4 --- /dev/null +++ b/docs/snippets/object-storage-transformation.mdx @@ -0,0 +1,75 @@ +import WebhookBatchInput from "/snippets/webhook-batch-input.mdx"; + +## Transformation contract + +Object storage connectors run the handler once per delivery batch. + + + +The handler must return: + +```ts +{ + config: { + format: "jsonl" | "json" | "raw"; + key: string; + }; + data: unknown[] | string; +} +``` + +| Return field | Behavior | +| --- | --- | +| `config.format` | `"jsonl"`, `"json"`, or `"raw"`. Defaults to `"jsonl"`. | +| `config.key` | Object name for this batch. A timestamp is appended after the transformation runs, so each batch produces a unique object. | +| `data` | The batch contents. Use an array for `"jsonl"` and `"json"`. Use a string for `"raw"`. | + +The default template returns the batch unchanged: + +```js +function handler(input) { + return { + config: { + format: "jsonl", + key: "object-generated-by-svix", + }, + data: input.events, + }; +} +``` + +With `"jsonl"`, each line is one event: + +```jsonl +{"payload":{"email":"joe@enterprise.io"},"eventType":"user.created"} +{"payload":{"id":12,"timestamp":"2025-07-21T14:23:17.861Z"},"eventType":"user.login"} +``` + +### Novu example + +This example groups objects by date and keeps each payload intact: + +```js +function handler(input) { + const partition = new Date().toISOString().slice(0, 10); + + const data = input.events.map((event) => ({ + eventType: event.eventType, + payload: event.payload, + })); + + return { + config: { + format: "jsonl", + key: `novu-events/dt=${partition}/events`, + }, + data, + }; +} +``` + +The connector appends a timestamp to `config.key`, so use the key for the prefix structure rather than adding a uniqueness suffix. + +### Batching behavior + +One delivered batch produces one object. Treat its contents as a variable-length batch and read it line by line when using `"jsonl"`. diff --git a/docs/snippets/webhook-batch-input.mdx b/docs/snippets/webhook-batch-input.mdx new file mode 100644 index 00000000000..755ad2ba639 --- /dev/null +++ b/docs/snippets/webhook-batch-input.mdx @@ -0,0 +1,7 @@ +The handler receives one delivery batch: + +- `input.events`, an array whose length is capped by the endpoint batch size +- `input.events[].eventType`, the Novu event type, for example `message.sent` +- `input.events[].payload`, the webhook body + +The webhook body wraps the resource in an envelope. `payload.object` is the resource type as a string, such as `"message"`, and the resource itself is under `payload.data.object`. For message events, fields such as `subscriberId` and `channel` are therefore at `event.payload.data.object`. Other event families use different resource shapes, so guard extracted fields with defaults. diff --git a/docs/snippets/webhook-connector-add-endpoint.mdx b/docs/snippets/webhook-connector-add-endpoint.mdx new file mode 100644 index 00000000000..d893fef9a92 --- /dev/null +++ b/docs/snippets/webhook-connector-add-endpoint.mdx @@ -0,0 +1,5 @@ + +## Add the endpoint + +Open **[Webhooks](https://dashboard.novu.co/webhooks)** in the Novu Dashboard, select **Endpoints**, click **Add Endpoint**, and choose **{connectorName}**. + diff --git a/docs/snippets/webhook-connector-related.mdx b/docs/snippets/webhook-connector-related.mdx new file mode 100644 index 00000000000..21652c5a1b9 --- /dev/null +++ b/docs/snippets/webhook-connector-related.mdx @@ -0,0 +1,5 @@ +## Related + +- [Webhook connectors overview](/platform/developer/webhooks/connectors) +- [Webhook event types](/platform/developer/webhooks/event-types) +- [Webhook delivery, retries, and recovery](/platform/developer/webhooks/webhooks#recovering-and-resending-failed-messages) diff --git a/docs/snippets/webhook-connectors-plan-note.mdx b/docs/snippets/webhook-connectors-plan-note.mdx new file mode 100644 index 00000000000..8cba87ddca6 --- /dev/null +++ b/docs/snippets/webhook-connectors-plan-note.mdx @@ -0,0 +1,3 @@ + + Outbound webhooks are available on [Team and Enterprise plans](https://novu.co/pricing). + From f03db9c0c2c292389a1e6ecdfe4add0b2536fe15 Mon Sep 17 00:00:00 2001 From: "cursor[bot]" <206951365+cursor[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:23:09 +0300 Subject: [PATCH 2/2] fix(root): resolve moderate decode-uri-component vulnerability fixes NV-8733 (#12516) Co-authored-by: Cursor Agent Co-authored-by: Dima Grossman --- pnpm-lock.yaml | 9 +++++---- pnpm-workspace.yaml | 5 +++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a3bdb44868..9569fda97b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,7 @@ overrides: degit@<2.8.6: ^2.8.6 liquidjs@<10.26.0: ^10.27.0 linkify-it@<5.0.2: ^5.0.2 + decode-uri-component@<0.5.0: ^0.5.0 '@codemirror/state': 6.6.0 '@codemirror/autocomplete': 6.18.3 @@ -18391,8 +18392,8 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - decode-uri-component@0.4.1: - resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} + decode-uri-component@0.5.0: + resolution: {integrity: sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==} engines: {node: '>=14.16'} decompress-response@6.0.0: @@ -48292,7 +48293,7 @@ snapshots: dependencies: character-entities: 2.0.2 - decode-uri-component@0.4.1: {} + decode-uri-component@0.5.0: {} decompress-response@6.0.0: dependencies: @@ -56701,7 +56702,7 @@ snapshots: query-string@9.1.0: dependencies: - decode-uri-component: 0.4.1 + decode-uri-component: 0.5.0 filter-obj: 5.1.0 split-on-first: 3.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c5fc9df0615..19b5b6137df 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -221,6 +221,11 @@ overrides: degit@<2.8.6: ^2.8.6 liquidjs@<10.26.0: ^10.27.0 linkify-it@<5.0.2: ^5.0.2 + # GHSA-vcc3-ghjq-m6fr: decode-uri-component <=0.4.2 DoS via exponential + # decoding of malformed percent-encoded input. Pulled transitively via + # pkg-pr-new > query-registry > query-string. Patched in >=0.4.3, but the + # only published patched release is 0.5.0. + decode-uri-component@<0.5.0: ^0.5.0 # Force single instances of @codemirror/state and @codemirror/autocomplete. # Without this, transitive lang-* packages pin to state@6.4.1 while direct # dashboard imports resolve against state@6.6.0, producing two virtual peer