Skip to main content
Novu does not provide a native Databricks connector. You can sync data from Databricks by calling the Novu REST API from a Databricks job. This guide covers three warehouse syncs: Use Databricks for data that is computed or stored in the warehouse. If an event must notify a user within seconds of happening in your product, trigger Novu from Kafka or another event stream instead of waiting for a warehouse job. If you already run reverse ETL, use the Hightouch guide instead of a custom notebook. For product analytics identify/track streams, use the Segment guide. The REST pattern below is the same if you call Novu from your own reverse ETL service.

How the sync works

The subscriber job processes at most 500 subscribers per request. The cohort job processes at most 100 topic subscriptions per request. The event job processes at most 100 events per request. The cohort job also calls POST /v2/topics to create the topic and GET /v2/topics/{topicKey}/subscriptions to read current membership.
Before you start, you need:
  • A Databricks workspace with permission to create jobs, secret scopes, and a checkpoint Volume
  • Unity Catalog Delta tables for subscribers, and optionally for cohorts and events
  • A Novu secret key from Developer > API Keys in the Novu Dashboard. Keys are environment-specific.
1

Store the Novu secret key

Create a Databricks secret scope and add the Novu secret key:
The second command prompts you for the secret value. Read the secret in the notebook:
Use https://eu.api.novu.co for an EU Novu environment. See API keys for hostnames.
A Novu secret key belongs to one environment and one region. Confirm that the key and API URL both point to the intended Novu environment before you run a backfill.
2

Add the HTTP and batching helpers

Add this cell once. All three syncs use it.
novu_request retries 429, 409, and temporary server errors. 409 is returned when idempotency is enabled and the same Idempotency-Key is still in flight. The helper uses the Retry-After header when Novu provides one.wait_for_budget sleeps before each write so the first request in a job is paced. It is a simple delay, not a shared token bucket across notebooks.The default pacing uses half of the Free plan’s Configuration and Event limits. Update CONFIGURATION_TOKENS_PER_SECOND and EVENT_TOKENS_PER_SECOND for your plan, but keep spare capacity if production traffic uses the same secret key. See Rate limiting for current limits and token costs.
3

Prepare the subscriber source

The source must contain one row per subscriber. Use a stable application ID for subscriber_id.The example below reads main.crm.novu_subscribers. Replace this name and the column mapping with your own table.
Sync subscriber profiles before topic subscriptions. Novu rejects a topic subscription if its subscriber does not exist.
4

Sync subscriber profiles

POST /v1/subscribers/bulk creates new subscribers and updates existing subscribers. Replaying a batch is safe because Novu upserts each record by subscriberId. See Bulk import.
toLocalIterator() streams rows to the driver instead of loading the full table into driver memory. The Novu API limits throughput, so a single paced sender is simpler and safer than starting HTTP clients on many Spark executors.Custom profile fields belong in data. Template code can read the example field as {{subscriber.data.plan}}. See Custom data on the subscriber profile.
5

Make the subscriber sync incremental

Use Delta Change Data Feed with Structured Streaming. Databricks stores progress in the checkpoint location, so later job runs process only new table versions.Enable Change Data Feed on the source table:
Create a Unity Catalog Volume for the checkpoint:
Replace the full-table call from the previous step with this stream:
On the first run, Databricks returns the current table snapshot as inserts. Later runs resume from the checkpoint. If a Novu request fails, sync_subscribers raises an error and Databricks does not commit that micro-batch to the checkpoint. The next run retries the batch.This example does not delete Novu subscribers when rows are deleted from the source table. Subscriber deletion should follow your product’s retention and account-deletion policy. Implement it as a separate reviewed process if required.
Change Data Feed records follow the Delta table retention policy. Run the job often enough that its checkpoint does not fall behind the retained table history. If the required source version is no longer available, restore from a known version or start a new checkpoint and run a full sync.
6

Sync a cohort to a topic

A Databricks cohort maps to a Novu topic. This example compares the desired warehouse membership with the current Novu membership, then sends only additions and removals.The job creates the topic before it reads membership. POST /v2/topics accepts an existing key, so calling it on every run is safe. See Automatic topic creation.
POST /v2/topics/{topicKey}/subscriptions creates the topic automatically when the key does not exist, so an explicit create is not required to add subscriptions. This job still creates the topic first for two reasons. Reading membership with GET /v2/topics/{topicKey}/subscriptions returns 404 when the topic does not exist, and creating the topic explicitly sets the display name shown in the Novu Dashboard.
Run this step after the subscriber sync:
The job reads the current membership from Novu on every run. If a prior run stops after some requests succeed, the next run recalculates the remaining difference instead of repeating the completed changes.Both the desired and existing membership sets are held in driver memory. For cohorts with millions of members, use a reverse ETL tool such as Hightouch, or store the Novu membership snapshot in Delta and calculate the difference with Spark.
7

Trigger workflows from warehouse events

A Databricks events table maps to Novu workflow triggers. Use this path for notifications that are computed in the warehouse, such as a daily digest or a trial-ending campaign. Do not use it for product actions that must notify within seconds.Keep this job separate from the subscriber sync so you can backfill profiles without sending historical notifications.The source must contain one row per notification to send. Use a stable event_id so retries do not send the same notification twice.POST /v1/events/trigger/bulk accepts at most 100 events per request.Use both of these on every bulk trigger:
  • Idempotency-Key on the HTTP request, derived from the event ids in that batch. This is the API-layer retry lock. See Idempotency. It is not enabled for every organization; contact support to turn it on.
  • transactionId set to the warehouse event_id. Use it to trace and cancel a run. It is not the same as an idempotency key. Concurrent retries can still enqueue twice if you rely on transactionId alone.
Novu can create a subscriber at trigger time from the to object. A prior subscriber sync is still useful so preferences and profile fields exist before the first notification.Do not call sync_events on the full table. The Change Data Feed job below is the production path.
name is the workflow identifier in Novu. payload fields are available in templates as {{payload.plan}}.orderBy("event_id") keeps bulk groups stable. If a micro-batch fails after some POSTs succeed, Databricks retries the same rows. An unordered toLocalIterator() can split those rows into different groups of 100, which would mint new Idempotency-Key values and send already-accepted events again. event_id must be unique so the sort order does not depend on Spark’s partition layout.Enable Change Data Feed on the events table, then process inserts only. Set EVENT_START_TIMESTAMP before the first run so the snapshot of historical rows is not sent.
startingTimestamp is applied only when the checkpoint is empty. After the first successful run, Databricks resumes from the checkpoint and ignores startingTimestamp. If you need a new start bound, use a new checkpoint path.
Do not replay the full events table. A replay consumes Event-bucket tokens even when Novu later ignores a duplicate transactionId.
8

Check the rate-limit settings

Novu uses separate token buckets for Configuration and Event endpoints. Subscriber and topic requests use the Configuration bucket. Workflow triggers use the Event bucket.The requests in this guide have these costs:The default helper uses half of the Free plan limits: 10 Configuration tokens per second and 30 Event tokens per second. At those settings, it sends one full subscriber batch every 10 seconds and one full event batch about every 3 seconds.Before increasing the rate:
  1. Check the Configuration and Events limits for your Novu plan in Rate limiting.
  2. Set CONFIGURATION_TOKENS_PER_SECOND and EVENT_TOKENS_PER_SECOND to those limits.
  3. Keep RATE_LIMIT_SHARE below 1.0 when other services use the same secret key.
  4. Monitor 429 responses and the RateLimit-Remaining response header.
Do not calculate throughput from request count alone. A bulk subscriber or bulk trigger request costs 100 tokens even when the request contains fewer than the maximum number of items.
9

Schedule and verify the jobs

Create three Databricks job tasks:
  1. Run the subscriber sync.
  2. Run the cohort sync after the subscriber task succeeds.
  3. Run the event trigger sync after the subscriber task succeeds. Cohort and event tasks can run in parallel.
For the first production run, filter each source to a small known set of subscribers. Verify the results before removing the filter. For events, start with rows added after a known timestamp so you do not notify on historical warehouse data.In the Novu Dashboard:
  1. Open Subscribers and confirm that profile fields match the source table.
  2. Open the topic and confirm that its subscriber count matches the cohort query.
  3. Open Activity Feed and confirm that warehouse events triggered the expected workflows.
  4. Check the Databricks task output for partial item failures or exhausted retries.
Set a job timeout and enable task retries. Subscriber bulk upserts by subscriberId. The cohort job diffs against Novu before writing. Event retries are safe when each bulk request sends a stable Idempotency-Key (and transactionId for tracing).

Troubleshooting

  • 401 Unauthorized: Confirm the Authorization header uses ApiKey, and confirm the secret key matches the Novu environment and API region.
  • 400 from the subscriber bulk endpoint: Confirm the body contains {"subscribers": [...]}, the array has no more than 500 items, and every subscriberId is a non-empty string.
  • Topic subscription failures: Run the subscriber sync first. Topic subscriptions require existing Novu subscribers.
  • Workflow not triggering: Confirm name matches an active workflow identifier in the same Novu environment as the secret key, and confirm to.subscriberId is present.
  • Duplicate notifications: Send an Idempotency-Key per bulk request. Set transactionId to the warehouse event_id for tracing. Process Change Data Feed inserts only, and set EVENT_START_TIMESTAMP before the first run.
  • 409 Conflict: The same Idempotency-Key is still in flight. The helper retries this. If it persists, wait and rerun the job.
  • 429 Too Many Requests: Lower RATE_LIMIT_SHARE, CONFIGURATION_TOKENS_PER_SECOND, or EVENT_TOKENS_PER_SECOND. Confirm those values match your plan.
  • Change Data Feed cannot find a version: The checkpoint is older than the retained Delta history. Run a full sync and start again with a new checkpoint location.

Subscribers

Subscriber identity, profile fields, and bulk import.

Topics

Topic keys, automatic creation, and triggering a workflow to a topic.

Manage topic subscriptions

Subscription identifiers, Context scoping, and JSON Logic conditions.

API keys

Secret keys, environment isolation, and API hostnames.

Rate limiting

Configuration and Events bucket limits, token costs, and 429 handling.

Trigger event

Single workflow trigger request shape, transactionId, and idempotency.

Bulk trigger event

POST /v1/events/trigger/bulk request shape and 100-event cap.

Idempotency

Idempotency-Key for safe retries of POST requests.

Hightouch

Reverse ETL when you do not want to run a custom Databricks notebook.