# Inbound webhook

> Accept leads from anything that can send an HTTP POST.
> Source: https://www.eventsiq.io/docs/inbound-webhook


An inbound webhook is a private EventsIQ address that accepts leads from anything that can send an
HTTP POST: Zapier, a call center's software, another form tool, or your own code. Each webhook source
gets its own address, and leads posted to it are mapped and routed like any other.

## Step 1: Create the endpoint

1. Go to **Forms**, click **New form**, give it a name, for example `Zapier - solar leads`, and pick
   **Webhook**.
2. On **Where leads go**, pick a routing and click **Continue**, or click **Decide later**.
3. Click **Create endpoint**.

EventsIQ shows:

| Value              | What it is                                                                  |
| ------------------ | --------------------------------------------------------------------------- |
| **Endpoint URL**   | `https://app.eventsiq.io/api/hooks/in/<key>`. The key in it is the password |
| **Signing secret** | Optional extra security. See [Signatures](#signatures)                      |
| **Try it**         | A ready to paste `curl` command                                             |

The address is always on the source page too: open the form from the **Forms** list, then its
**Connection** tab, under **Address to send leads to**.

![A webhook's address](https://www.eventsiq.io/docs-media/inbound-webhook/01-endpoint.png "The source page's Connection tab: Address to send leads to, with the address and its key shown as YOUR_ENDPOINT_KEY, Require a signature, and Developer details closed.")

## Step 2: Send a lead

Point the sending system at the **Endpoint URL** and send a POST:

```bash
curl -X POST "https://app.eventsiq.io/api/hooks/in/YOUR_ENDPOINT_KEY" -H "Content-Type: application/json" -d '{"first_name":"Jane","last_name":"Doe","email":"jane@example.com","phone":"5551230000","state":"TX"}'
```

EventsIQ accepts:

-   **JSON**: an object. Anything else is wrapped as `{"payload": ...}`.
-   **Form encoded**: `application/x-www-form-urlencoded`, like a plain HTML form.
-   Bodies up to **128 KB**.

The reply tells the sender what happened:

| Reply    | Meaning                                                                            |
| -------- | ---------------------------------------------------------------------------------- |
| **200**  | `{"received":true,"leadId":"...","duplicate":false}`. Stored                        |
| **400**  | The body could not be read                                                         |
| **401**  | `Invalid signature.` Only when signatures are required                             |
| **404**  | `Unknown endpoint.` The address is wrong, or the source is turned off              |
| **413**  | The body is over 128 KB                                                            |
| **429**  | Too many requests from one address: more than 300 a minute. Wait for `Retry-After` |

## Step 3: Match up the fields

Every top level key in the body becomes an answer you can map on **Match up the fields**:
`first_name`, `email` and so on. Nested objects are reachable through mapping paths. EventsIQ keeps
the last ten payloads, so you can map against real data. See [Fields](https://www.eventsiq.io/docs/fields).

## Passing attribution

If the sender knows where the lead came from, it can send that too, as ordinary fields in the body or
in the address's query string. These keys are kept as the lead's attribution, available as
`{{runtime.key}}`:

| Purpose           | Keys                                                                                     |
| ----------------- | ---------------------------------------------------------------------------------------- |
| Click ids         | `gclid`, `fbclid`, `fbc`, `fbp`, `msclkid`, `newsbreak_cid`, `aleid`, `alart`, `ttclid`, `ttp`, `sccid`, `scid`, `click_id` |
| UTM tags          | `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content`                    |
| Pages             | `landing_url`, `page_url`, `referrer`                                                    |
| The visitor       | `client_ip_address`, `client_user_agent`                                                 |
| Your own ids      | `sub_id`, `affiliate_id`, `session_id`, `visitor_id`                                     |
| EventsIQ tracking | `traffic_source_id`, `eiq_campaign_id`, `eiq_click_id`                                   |

The most useful one is **`eiq_click_id`**. If the lead first came through an EventsIQ
[campaign link](https://www.eventsiq.io/docs/click-tracking), send the click id back and the lead is joined to its click:
campaign, ad and all. A `gclid` sent this way is used by the
[Google postback](https://www.eventsiq.io/docs/global-events#google-postback). In the same way, `msclkid` is used for
[Microsoft Ads](https://www.eventsiq.io/docs/microsoft-ads), `newsbreak_cid` for [NewsBreak](https://www.eventsiq.io/docs/newsbreak), and `aleid`
for [AppLovin](https://www.eventsiq.io/docs/applovin).

:::note{title="Older names still work"}
`eiq_click_id` and `eiq_campaign_id` used to be called `formwork_click_id` and
`formwork_campaign_id`. A sender that still posts the old names keeps working, with no end date, and
`{{runtime.formwork_click_id}}` in a body template still fills in. New setups should use the `eiq_`
names.
:::

### The visitor's IP and browser

EventsIQ records the IP address and browser of whatever connects to it, and for a webhook that is
the sending server, not the person. EventsIQ never sends those to Meta, TikTok, Snapchat, Google,
NewsBreak or AppLovin as the visitor's.

If your system collected the lead in a browser, pass the person's own details:

-   **`client_ip_address`**: the visitor's IP, for example `73.12.45.9`. Anything that is not a valid
    IP is ignored.
-   **`client_user_agent`**: the visitor's browser string.

They are sent to the networks as the visitor's IP and browser. With `client_user_agent` and
`page_url` both present, a Meta event is sent as a **website** event, which Meta matches better than a
system event. AppLovin needs both on every event, so a webhook lead without them is skipped for AppLovin.

:::note{title="What a webhook cannot set"}
The received time and the connecting IP and browser are always set by EventsIQ. A webhook also cannot
set `campaign_id` or `ad_id` directly: send `eiq_click_id` instead and EventsIQ fills them in from
the click.
:::

## Duplicates

Systems retry. To stop one lead being sold twice:

-   Send an **`Idempotency-Key`** header (or `X-Idempotency-Key`) that is unique per lead. A repeat
    with the same key is answered with `"duplicate":true` and not stored again.
-   Without one, an identical body posted again within the same clock hour is treated as a
    duplicate.

## Signatures

The endpoint key in the address is enough to start with. For more protection, turn on **Require a
signature** on the source page's **Connection** tab. Every request must then carry an
`X-EventsIQ-Signature` header: the HMAC-SHA256 of the raw body, in hex, using the **Signing secret**,
which is under **Developer details** on the same tab. A `sha256=` prefix is accepted.

A sender set up before the rename may still send the same signature as `X-Formwork-Signature`. That
is accepted for now and will stop being read after 13 January 2027, so switch the header name when
you next touch the sender.

When signing is on, the **Example request** under **Developer details** shows how to compute it.

:::caution{title="Making a new secret"}
**Make a new secret** beside the signing secret replaces it, and the old one stops working at once.
Update the sender before or straight after you do.
:::

## Related

-   [Fields](https://www.eventsiq.io/docs/fields): mapping the body's keys.
-   [How click tracking works](https://www.eventsiq.io/docs/click-tracking): what `eiq_click_id` joins.
-   [Destinations](https://www.eventsiq.io/docs/destinations): the outbound side, sending leads on to destinations.
