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

# Campaign Mapping

> Route events from a single agency-level sequencer webhook to the correct client, based on keyword matches against the campaign name.

## Overview

If you run several client workspaces off **shared, agency-level accounts** in Instantly, Smartlead, EmailBison, Lemlist, Sendkit, or HeyReach, you don't have to configure and maintain a separate webhook per client per integration. **Campaign Mapping** lets you point a single webhook — one per integration, shared across the whole agency — at RevyOps, and have RevyOps figure out which client each incoming event belongs to and route it there.

Routing is based on a **keyword match** against the campaign (or workspace) name in the event payload. You assign one or more keywords to each client; when an event's campaign name contains one of a client's keywords, RevyOps forwards ("fans out") that event to that client, and all of the campaign's data — sends, replies, bounces, tags — flows into that client's database and reports exactly as if the client had its own dedicated webhook.

<Note>
  Campaign Mapping is an agency-admin feature. The page lives at `/dashboard/campaign-mapping` under **Settings → Connect**, and is only accessible to users with the **admin** role — anyone else is redirected away.
</Note>

## How it works

1. Your sequencer platform (Instantly, Smartlead, etc.) sends every campaign event to one shared webhook URL for the whole agency, instead of one URL per client.
2. RevyOps looks up every client that has agency-level mapping turned on for that integration, and checks each one's list of keywords against the campaign name (or, for EmailBison, the workspace name) on the incoming event.
3. Every client with a matching keyword gets the event enqueued as a background task and delivered to its normal per-client webhook processing — the same processing path a dedicated per-client webhook would use.
4. If more than one client's keywords match, the event is fanned out to **all** of them.
5. If no client's keywords match, the event is dropped (logged, not delivered) — it doesn't get retried later.

Keyword matching is a **case-sensitive substring match**: a keyword matches if it appears anywhere inside the campaign name, not just as a whole word. For example, a keyword of `Acme` matches a campaign named `Acme - Q3 Outbound`, but a keyword of `acme` (lowercase) would not match `Acme - Q3 Outbound` since the case differs.

<Warning>
  Keyword matching is case-sensitive. Enter keywords using the exact casing your sequencer uses in campaign names, and be careful with short or generic keywords — a keyword like `Q3` will match any campaign name containing that substring, across every client.
</Warning>

## Setting up an agency-level webhook

<Steps>
  <Step title="Open Campaign Mapping">
    Go to **Settings → Connect → Campaign Mapping**.
  </Step>

  <Step title="Pick the integration">
    Under **Webhooks**, select the sequencer platform (Instantly, EmailBison, Smartlead, Lemlist, Sendkit, or HeyReach) whose webhook you're setting up.
  </Step>

  <Step title="Copy the webhook URL">
    Copy the URL shown in the **Webhook URL** field using the copy icon. It's shaped like `{your-revyops-origin}/api/public/global-{integration}-webhook/{your-agency-id}` — one fixed URL per integration for your whole agency.
  </Step>

  <Step title="Paste it into the sequencer">
    In the sequencer platform's own webhook settings (at the account/workspace level, not per-campaign), paste this URL as the webhook destination.
  </Step>
</Steps>

Repeat this for each integration you use with a shared agency-level account.

## Assigning keywords to a client

In the **Clients** table, find the client and click the pencil icon (or click a client's `+N more` keyword chip) to open **Edit Campaign Keywords**.

* Type a keyword and press **Add** (or hit Enter) to add it to the client. Duplicate keywords are ignored.
* Click the trash icon on a keyword chip to remove it.
* The dialog auto-saves when you close it — there's no separate "cancel without saving" action once you've made a change.

Each client can have any number of keywords. A campaign matches a client if **any** of that client's keywords appears in the campaign name.

<Note>
  **EmailBison works differently.** Because EmailBison's webhook payload also carries the instance URL and workspace name (rather than a per-event campaign name in every payload), a client only matches when **both** conditions hold: the client's stored EmailBison API URL hostname matches the event's instance URL hostname, **and** the event's workspace name exactly equals one of the client's keywords (not a substring match — the whole workspace name must match a keyword exactly, case-insensitively). If a client's EmailBison API URL isn't configured on their integration settings, EmailBison events can never match that client via agency mapping.
</Note>

## Enabling agency-level mapping per integration

Keywords alone don't route anything — you also need to check which integrations should use agency-level mapping for that client. In the same **Edit Campaign Keywords** dialog, under **Integrations with mapping**, check the box for each platform where this client's events should come from the shared agency webhook rather than its own individual webhook.

<Warning>
  Once an integration is checked for a client, RevyOps stops processing events sent to that client's own **individual** webhook for that integration — they're silently ignored to avoid double-counting the same event through two paths. If you enable agency-level mapping for a client, make sure that client's sequencer campaigns are actually pointed at the agency's shared webhook (or have been migrated off their old per-client webhook), not still sending to both.
</Warning>

Unchecking an integration for a client reverts to that client's individual webhook — agency-level events stop being forwarded to it, and its direct webhook resumes being processed normally.

## Configuring label mapping

Even after an integration is routing correctly, RevyOps still needs to know which lead statuses or tags in the sequencer mean **Interested** and which mean **Do Not Contact**, so that fanned-out events get classified correctly once they land in the client's data. This is configured per client, per integration.

In the **Integrations with mapping** table column, each integration chip shows a status:

* **Green with a check** — label mapping is configured for that client/integration.
* **Yellow with a warning icon** — label mapping isn't configured yet. Hovering explains that events carrying tags will fail to process until it's set up.

To configure labels, click **Configure {Integration}** (shown in the warning banner inside the edit dialog when an unconfigured integration is selected) to open the **Configure Labels** flow:

<Steps>
  <Step title="Enter API credentials">
    Enter the client's API key for that integration (and, for EmailBison, its API URL). RevyOps pings the sequencer's API to verify the key before continuing — an invalid key shows an error and clears the stored value.
  </Step>

  <Step title="Classify statuses">
    RevyOps fetches the sequencer's actual lead statuses or tags and lists them for classification. Check each one as **Interested** or, if the integration supports it, as a **Blocked Status** that should trigger Do Not Contact. RevyOps pre-checks some statuses automatically based on name heuristics (e.g. names containing "interested," "meeting booked," or "demo booked" are suggested as Interested; names starting with "not," "un-," "lost," "bounced," or similar are left unchecked). Review the suggestions — they're a starting point, not a final answer.
  </Step>

  <Step title="Save">
    Click **Save Labels**. This persists the classification for that client/integration and flips its status chip to configured.
  </Step>
</Steps>

<Note>
  **Lemlist only supports Do Not Contact classification** — there's no Interested step for Lemlist. Its "statuses" are a fixed list of event types (Unsubscribed, Not Interested, Bounced, Send Failed, Failed) rather than custom sequencer tags, and you choose which of those should trigger Do Not Contact.
</Note>

## HeyReach: tag events without a campaign name

Most events carry a campaign (or workspace) name, so the keyword match described above is enough to route them. HeyReach's **tag and auto-tag events** (used when a lead is marked Interested or Converted) are an exception — those payloads don't include a campaign name at all, so keyword matching can't route them.

For these specific event types, RevyOps instead looks at the lead's LinkedIn profile URL and finds which of the agency's HeyReach-mapped clients already has LinkedIn message history with that lead (i.e., which client actually ran a HeyReach sequence to them). The event is routed to whichever client(s) have that history. If the same lead has LinkedIn message history with more than one client, the event is fanned out to all of them. If no mapped client has any message history with the lead, the event is dropped and logged.

Regular HeyReach message events (which do carry a campaign name) still route by the normal keyword match against the campaign name.

## Troubleshooting

* **Events aren't reaching any client.** Check that the client's keyword actually appears (with matching case) in the campaign name your sequencer is sending, and that the correct integration checkbox is checked for that client. For EmailBison, also confirm the client's stored API URL hostname matches the instance sending the webhook, and that the keyword equals the workspace name exactly.
* **An event went to the wrong client, or to more than one.** Two clients' keyword lists can both match the same campaign name — keywords aren't required to be unique across clients, and a short or generic keyword increases the odds of an unintended match. Tighten the keyword (make it longer or more specific) on the client(s) that shouldn't have matched.
* **"Campaign events with tags will fail for this integration."** The client/integration combination is missing label mapping. Open **Configure Labels** for that integration and complete both steps.
* **Duplicate data after turning on agency mapping.** Confirm the client's own individual webhook in the sequencer platform has actually been removed or repointed to the agency URL — RevyOps ignores events sent to a client's individual webhook once agency mapping is enabled for that integration, but if the sequencer is still also configured to hit some other endpoint outside RevyOps, that's outside RevyOps's control.
* **HeyReach Interested/Converted tags aren't updating a contact.** These events route by LinkedIn message history rather than keywords. Confirm the client actually has prior HeyReach message history with that lead — if the lead was never messaged through that client's HeyReach account, there's nothing for RevyOps to match on and the event is dropped.
