- Subscriber profiles, such as email address, locale, and plan
- Cohort membership, represented by topic subscriptions
- Notification events, which trigger workflows
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
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 forsubscriber_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: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.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.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 stableevent_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-Keyon 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.transactionIdset to the warehouseevent_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 ontransactionIdalone.
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.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:
- Check the Configuration and Events limits for your Novu plan in Rate limiting.
- Set
CONFIGURATION_TOKENS_PER_SECONDandEVENT_TOKENS_PER_SECONDto those limits. - Keep
RATE_LIMIT_SHAREbelow1.0when other services use the same secret key. - Monitor
429responses and theRateLimit-Remainingresponse header.
9
Schedule and verify the jobs
Create three Databricks job tasks:- Run the subscriber sync.
- Run the cohort sync after the subscriber task succeeds.
- Run the event trigger sync after the subscriber task succeeds. Cohort and event tasks can run in parallel.
- Open Subscribers and confirm that profile fields match the source table.
- Open the topic and confirm that its subscriber count matches the cohort query.
- Open Activity Feed and confirm that warehouse events triggered the expected workflows.
- Check the Databricks task output for partial item failures or exhausted retries.
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
Authorizationheader usesApiKey, 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 everysubscriberIdis a non-empty string. - Topic subscription failures: Run the subscriber sync first. Topic subscriptions require existing Novu subscribers.
- Workflow not triggering: Confirm
namematches an active workflow identifier in the same Novu environment as the secret key, and confirmto.subscriberIdis present. - Duplicate notifications: Send an
Idempotency-Keyper bulk request. SettransactionIdto the warehouseevent_idfor tracing. Process Change Data Feed inserts only, and setEVENT_START_TIMESTAMPbefore the first run. - 409 Conflict: The same
Idempotency-Keyis 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, orEVENT_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.
Related documentation
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.