> ## Documentation Index
> Fetch the complete documentation index at: https://docs.novu.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Hightouch

> Learn how to set up Hightouch as a data source for Novu using the HTTP Request destination. Sync warehouse data to create subscribers and trigger notification workflows in Novu.

This guide demonstrates how to use Hightouch's HTTP Request destination to sync data from your warehouse into Novu. You'll learn how to:

* Create a reusable HTTP Request destination that points at the Novu API
* Sync rows from a Hightouch model into Novu subscribers
* Trigger notification workflows in Novu from a Hightouch events model
* Configure retries and rate limits for reliable delivery

By the end, you'll have a working integration that creates subscribers and triggers notification workflows in Novu based on the data in your warehouse.

<Info>
  Before you start, ensure you have:

  * A **Hightouch account** with a connected **source** (a warehouse or database such as Snowflake, BigQuery, or Postgres) and permission to create destinations and syncs
  * At least one **model** in Hightouch that returns the rows you want to sync (for example, a table of users)
  * A **Novu account** with an **API key** (find this in your Novu dashboard under **Settings** > **API Keys**)
</Info>

<Note>
  Hightouch is a reverse ETL platform. Instead of running code on each event like [Segment Destination Functions](/guides/analytics/segment), Hightouch queries a model on a schedule and sends an HTTP request for each row that is added, changed, or removed. You map those row changes to Novu API calls.
</Note>

<Steps>
  <Step>
    ## Create the HTTP Request destination

    Create one destination per service and reuse it across syncs.

    1. Go to the **Destinations** overview page and click **Add destination**
    2. Select **HTTP Request** and click **Continue**
    3. Enter the **Base URL** for the Novu API:
       * US (default): `https://api.novu.co`
       * EU: `https://eu.api.novu.co`
    4. Under **HTTP headers**, add the following headers. Mark the `Authorization` value as **Secret** so it is encrypted and hidden in the UI:

    | Header          | Value                      |
    | --------------- | -------------------------- |
    | `Authorization` | `ApiKey YOUR_NOVU_API_KEY` |
    | `Content-Type`  | `application/json`         |

    5. Leave the certificate options off unless your setup requires them, then click **Continue**
    6. Give the destination a descriptive name, such as `HTTP Request - Novu API`, and save

    <Tip>
      The base URL is the static part of the endpoint. You add the specific path (for example, `/v2/subscribers`) later in each sync, so a single Novu destination can power both the subscriber sync and the workflow trigger sync.
    </Tip>
  </Step>

  <Step>
    ## Sync a model into Novu subscribers

    This sync keeps Novu subscribers in step with the users in your warehouse. Use one request shape for both new and updated rows: `POST /v2/subscribers` creates a subscriber when the `subscriberId` is new and updates it when it already exists.

    Assume your model returns one row per user with columns like this:

    | `subscriber_id` | `first_name` | `last_name` | `email`             | `phone`      |
    | --------------- | ------------ | ----------- | ------------------- | ------------ |
    | `97980cfea0067` | Peter        | Gibbons     | `peter@example.com` | +14158675309 |

    1. Go to the **Syncs** overview page and click **Add sync**
    2. Select your users **model** and the **Novu** HTTP Request destination
    3. Under **request triggers**, enable **Rows added** and **Rows changed**
    4. Configure **both** triggers the same way:

    * **HTTP method**: `POST`
    * **URL**: `/v2/subscribers`
    * **Payload type**: JSON
    * **Define JSON payload**:

    ```liquid theme={null}
    {
      "subscriberId": "{{ row.subscriber_id }}",
      "firstName": "{{ row.first_name }}",
      "lastName": "{{ row.last_name }}",
      "email": "{{ row.email }}",
      "phone": "{{ row.phone }}"
    }
    ```

    <AccordionGroup>
      <Accordion title="Explanation">
        * Map `subscriberId` to a stable warehouse column that never changes. Novu uses it as the recipient identifier.
        * `POST /v2/subscribers` is idempotent for a given `subscriberId`: the first request creates the subscriber, later requests update the same record.
        * Wrap every string value in double quotes inside the JSON payload. Leave numeric or boolean values unquoted.
        * Use Hightouch's **Preview** tab to confirm the rendered body before you save.
      </Accordion>

      <Accordion title="Optional: partial updates with PATCH">
        If you only want to send changed fields and not a full profile, configure **Rows changed** as:

        * **HTTP method**: `PATCH`
        * **URL**: `/v2/subscribers/{{ row.subscriber_id }}`
        * Body with only the fields you want to update

        Prefer the shared `POST` upsert path above unless you specifically need partial updates. `PATCH` returns 404 if the subscriber does not exist yet.
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step>
    ## Trigger workflows from an events model

    To trigger a Novu workflow, sync a model that returns one row per event you want to notify on. Keep this separate from the subscriber sync so you can schedule and backfill them independently.

    Novu can [create a subscriber just in time](/platform/concepts/subscribers#just-in-time) when a workflow is triggered. A prior subscriber sync is useful for preferences and enrichment, but it is not required for triggers to succeed.

    A simple events model returns the target subscriber, the workflow to run, and the fields you need in the payload:

    | `subscriber_id` | `workflow_id` | `plan`       | `account_type` |
    | --------------- | ------------- | ------------ | -------------- |
    | `97980cfea0067` | `welcome`     | `Pro Annual` | `Facebook`     |

    1. Create a new sync from your events **model** to the **Novu** destination
    2. Under **request triggers**, enable **Rows added** only, so each new event fires once
    3. Configure the trigger:

    * **HTTP method**: `POST`
    * **URL**: `/v1/events/trigger`
    * **Payload type**: JSON
    * **Define JSON payload**:

    ```liquid theme={null}
    {
      "name": "{{ row.workflow_id }}",
      "to": {
        "subscriberId": "{{ row.subscriber_id }}"
      },
      "payload": {
        "plan": "{{ row.plan }}",
        "accountType": "{{ row.account_type }}"
      }
    }
    ```

    <AccordionGroup>
      <Accordion title="Explanation">
        * `name` is the workflow identifier in Novu. Store it in a column so one sync can drive many workflows, or hard-code a static value like `"welcome"` if the model only feeds one workflow.
        * `to.subscriberId` addresses the recipient. You can also pass profile fields such as `email` or `firstName` inside `to` so Novu upserts them on trigger.
        * Build `payload` from typed columns in the Liquid template. Prefer this over injecting a pre-serialized JSON string from the warehouse, which can escape incorrectly when the column is stored as text.
      </Accordion>
    </AccordionGroup>

    <Warning>
      Enable only the **Rows added** trigger for workflow sends. Enabling **Rows changed** or **Rows removed** on an events model can trigger the same notification more than once.
    </Warning>
  </Step>

  <Step>
    ## Configure reliability and run the sync

    Before your first run, set rate limits and error handling so you stay within Novu's limits and recover cleanly from transient failures.

    1. **Rate limiting and concurrency**: Hightouch defaults to 1000 requests per second. Novu's Free plan allows **20 RPS** for subscriber and configuration endpoints and **60 RPS** for event triggers. Higher plans raise those caps. See [Rate limiting](/api-reference/rate-limiting). Start below your plan's limit (for example, 10 to 20 RPS for subscriber syncs and 30 to 60 RPS for triggers on Free), then raise the limit if your plan allows it.
    2. **Error handling**: Hightouch treats any `400` or `500` level response as an error and can retry until the request succeeds. Retrying on the next sync run is appropriate for **429** and **5xx** responses. Permanent client errors such as **400**, **401**, **404**, and **422** usually mean a bad payload, key, or template. Fix those with the live debugger and alerts rather than relying on endless retries.
    3. **Initial sync behavior**: decide how existing rows are handled on the first run. For a subscriber backfill, sync all rows. For workflow triggers, skip existing rows so you do not notify users about historical events.
    4. Set a **schedule** or run the sync manually, then click **Run**.
  </Step>

  <Step>
    ## Verify the integration

    <AccordionGroup>
      <Accordion title="1. Confirm subscribers">
        After the subscriber sync completes, open the **Subscribers** list in your Novu dashboard and confirm the rows appear with the expected `subscriberId`, name, email, and phone.
      </Accordion>

      <Accordion title="2. Confirm workflow triggers">
        After the events sync completes, open the **Activity Feed** in your Novu dashboard and confirm the workflow ran for the expected subscriber with the payload from your model.
      </Accordion>
    </AccordionGroup>

    Use Hightouch's **live debugger** on the sync run to inspect the exact request and response for each row, which makes it easy to spot payload or authentication issues.
  </Step>
</Steps>

<AccordionGroup>
  <Accordion title="Troubleshooting">
    * **401 Unauthorized**: Check the `Authorization` header on the destination. It must be `ApiKey YOUR_NOVU_API_KEY`, and the key must match your Novu environment (Development or Production) and region.
    * **Workflow not triggering**: Confirm the value in `name` matches an existing workflow identifier in Novu, the workflow is active, and you are using an API key from the same environment.
    * **Invalid JSON payload**: Make sure every string is wrapped in double quotes. Use Hightouch's payload **Preview** against a sample row before running the sync.
    * **429 Too Many Requests**: Lower the rate limit and concurrency in the sync configuration to stay within your [plan limits](/api-reference/rate-limiting).
  </Accordion>

  <Accordion title="Additional notes">
    * **One destination, many syncs**: The Novu destination holds the base URL and credentials. Add a new sync for each endpoint you need rather than creating a second destination.
    * **Batching workflow triggers**: `POST /v1/events/trigger` expects a single event. For batches, use `POST /v1/events/trigger/bulk` with an `events` array. Bulk requests cost 100 rate-limit tokens each. Example Liquid body when batching is enabled:

    ```liquid theme={null}
    {
      "events": [
        {% for row in rows %}
          {
            "name": "{{ row.workflow_id }}",
            "to": {
              "subscriberId": "{{ row.subscriber_id }}"
            },
            "payload": {
              "plan": "{{ row.plan }}",
              "accountType": "{{ row.account_type }}"
            }
          }{% unless forloop.last %},{% endunless %}
        {% endfor %}
      ]
    }
    ```

    * **Region**: EU accounts must use `https://eu.api.novu.co`. A US API key will not authenticate against the EU host, and the reverse is also true.
  </Accordion>
</AccordionGroup>

With this setup, changes in your warehouse flow into Novu on every sync, keeping subscribers current and triggering notification workflows from the data you already trust.

## Related guides

* [Segment Destination Functions](/guides/analytics/segment)
* [Rate limiting](/api-reference/rate-limiting)
