# Webhooks: push every send to your system

> In Webhooks, create a webhook with method, URL, headers and a JSON body with variables such as {{ lead.name() }}. Test it with a sample contact, attach up to 3 webhooks to an email campaign (or use webhooks only) and track every delivery in the logs. If the destination fails, BIP tries up to 6 times, every 60 seconds.

Fonte: https://wiki.bip.marketing/en-us/webhooks · Atualizado: 2026-10-08

<UseCaseMeta effort="nocode" channel="webhook" plan="free" />

Every time a campaign reaches a contact, BIP can notify another system: create the contact in your CRM, post a message for your team, feed a spreadsheet. You build the request once in the panel, with the contact's data in the right place, and BIP does the rest with every send.

<Checklist items="A BIP account (the free plan works)|The URL that will receive the data, plus the access token if the destination requires one|An email campaign, recurring, one-time or API-triggered, to attach the webhook to" />

## What you'll get

- Every contact in the campaign lands in your CRM or tool, with the name, email, phone, city, fields and KPIs you choose.
- Everything in the panel, no code: method, URL, headers and body, with variable suggestions and a preview.
- Deliveries that recover on their own: if the destination fails, BIP tries again.
- Logs with the request and response for every delivery, plus metrics per webhook.
- Webhook-only campaigns, to sync systems without sending email.

## How it works

<Steps items="Create the webhook with URL, headers and body|Test it with a sample contact and check the log|Attach it to a campaign: every processed contact fires the webhook" />

A webhook is an HTTP request that BIP makes to your system. You define the method, the URL, the headers and the JSON body. In the body, variables such as `{{ lead.name() }}` turn into each contact's data at send time.

The webhook runs when it's attached to a campaign. For each contact the campaign processes, BIP builds the body with that person's data and sends it. It works with **One-time**, **Recurring** and **API Trigger** campaigns. People who unsubscribed from all communications don't fire the webhook.

A delivery succeeds when your system responds with a 2xx code (200, 201, 204…). Any other response, or no response at all, triggers a retry 60 seconds later. There are up to 6 attempts in total: the first one plus 5 more.

## Step by step

### 1. Create the webhook

1. In the menu, open **Webhooks** and click **New Webhook**. BIP creates an **[untitled]** webhook and opens its page.
2. In **Webhook Configuration**, choose the method. To send the contact's data, use **POST** (or **PUT**, if the destination requires it).
3. Paste the URL that will receive the data in place of `http://your-webhook-endpoint.com`.
4. Click **Save**.

<Shot src="/img/webhooks/1-en-us.png" alt="Webhook page with method and URL in Webhook Configuration" url="dash.bip.marketing/…/webhooks/…" />

### 2. Build the headers and the body

1. Open **Headers** and write a JSON object. Always include `"Content-Type": "application/json"`: BIP sends the body as JSON and most systems check this header. If the destination requires a token, add it too, for example `"Authorization": "Bearer YOUR_TOKEN"`.
2. In **Body Template**, write the JSON the destination expects. Inside a quoted value, type `{{` and pick the variable from the suggestion list. The help icon next to **Body Template** shows every variable.
3. Check the **Preview** on the right: it shows the finished body, filled in with a sample contact's data, and updates right after you stop typing.
4. Click **Save**.

The body must be valid JSON to save. That's why every variable goes inside quotes and reaches the destination as text: `"vip": "{{ lead.hasTag('vip') }}"` becomes `"vip": "true"`.

For a fallback value when the data is empty, use `||`: `"saudacao": "Hi, {{ lead.firstName() || 'there' }}"`.

<Shot src="/img/webhooks/2-en-us.png" alt="Webhook headers, body template with variables and preview" url="dash.bip.marketing/…/webhooks/…" />

<Callout type="tip">
For custom fields, KPIs and tags, always pick from the suggestion list. The list shows the name you know and inserts the internal identifier BIP uses to find the data.
</Callout>

### 3. Name and organize it

1. At the top of the page, click **Settings**.
2. Fill in the **Name** (it's what you see when attaching it to campaigns), choose an icon and, if you like, add a **Description**.
3. In **Group**, choose a group to organize the list. Webhook groups are created in **Settings** → **Groups**.
4. In **Status**, leave **Active** on.
5. Click **Save**.

<Shot src="/img/webhooks/3-en-us.png" alt="Webhook settings with name, icon, description, group and status" url="dash.bip.marketing/…/webhooks/…" />

### 4. Test it with a sample contact

1. With everything saved, click **Test Webhook**. The button is available when there are no unsaved changes.
2. BIP sends a real request to your URL, with a sample contact (made-up name, email, phone and cities; empty custom fields), and opens **Webhook Stats**.
3. In **Delivery Logs**, click **View** on the test row. **Request** shows what BIP sent; **Response** shows the code and body your system returned.

The test really reaches your system. If the destination is your production CRM, delete the test record afterwards, or point the webhook at a test environment first.

<Shot src="/img/webhooks/4-en-us.png" alt="Webhook stats with the delivery log for the test" url="dash.bip.marketing/…/webhooks/stats/…" />

### 5. Attach it to a campaign

1. In **Campaigns**, open a campaign or click **New Campaign**, with **Email** as the **Channel**.
2. In the **Webhooks** section, select up to 3 webhooks. Every processed contact fires all of them.
3. For a **webhook-only** campaign, don't choose an **Email Template** and select at least one webhook. No email goes out: each contact only fires the webhooks.
4. Choose the **Schedule Type** (**One-time**, **Recurring** or **API Trigger**), then save and activate it like any other campaign.

<Shot src="/img/webhooks/5-en-us.png" alt="Webhooks section of an email campaign with webhooks selected" url="dash.bip.marketing/…/campaigns/…" />

<PlanOnly plan="full">
In **Flows**, the **Send Webhook** step uses the same webhooks: the contact fires the webhook when it reaches that point in the flow.
</PlanOnly>

## Reference

### Webhook configuration

| Option        | How it works                                                            |
| ------------- | ----------------------------------------------------------------------- |
| Method        | GET, POST, PUT or DELETE. To send data in the body, use POST or PUT.    |
| URL           | Destination address. Fixed: it doesn't accept variables.                |
| Headers       | JSON object, such as `{"Content-Type": "application/json"}`. Fixed.     |
| Body Template | Valid JSON, with contact variables inside quotes.                       |
| Name, icon    | How the webhook appears in the list and in campaigns.                   |
| Description   | Free text, for your team.                                               |
| Group         | Organizes the **Webhooks** list.                                        |
| Status        | **Active** or inactive.                                                 |

### Delivery and retries

| Rule                  | Value                                                        |
| --------------------- | ------------------------------------------------------------ |
| Success               | Response with a 2xx code                                     |
| Retry                 | Any other code, or no response                               |
| Interval              | 60 seconds                                                   |
| Total attempts        | 6 (the first one plus 5 more)                                |
| After the 6th failure | The campaign logs a webhook error for that contact           |
| Webhooks per campaign | Up to 3, in Email channel campaigns, with or without email   |

### Contact variables

Use them inside a quoted value. The result arrives as text.

| Variable                                | What it returns                                        |
| --------------------------------------- | ------------------------------------------------------ |
| `{{ lead.name() }}`                     | Full name                                              |
| `{{ lead.firstName() }}`                | First name                                             |
| `{{ lead.middleName() }}`               | Middle names                                           |
| `{{ lead.lastName() }}`                 | Last name                                              |
| `{{ lead.email() }}`                    | Email                                                  |
| `{{ lead.id() }}`                       | Contact ID in BIP                                      |
| `{{ lead.namespace() }}`                | Account namespace                                      |
| `{{ lead.createdAt() }}`                | Creation date, in ISO 8601 format                      |
| `{{ lead.createdAt('YYYY-MM-DD') }}`    | Formatted creation date                                |
| `{{ lead.updatedAt() }}`                | Last update, in ISO 8601 format                        |
| `{{ lead.updatedAt('YYYY-MM-DD') }}`    | Formatted last update                                  |
| `{{ lead.metadata('[METADATA_KEY]') }}` | One metadata value: replace `[METADATA_KEY]` with the key |
| `{{ lead.sourceType('first') }}`        | First source type (`api`, `internal`, `page`…)         |
| `{{ lead.sourceType('last') }}`         | Last source type                                       |

### Phone variables

| Variable                                           | Example                              |
| -------------------------------------------------- | ------------------------------------ |
| `{{ lead.phone('e164') }}`                         | `+5511912345678`                     |
| `{{ lead.phone('international') }}`                | `+55 11 91234-5678`                  |
| `{{ lead.phone('national') }}`                     | `(11) 91234-5678`                    |
| `{{ lead.phone('nationalNumber') }}`               | `11912345678`                        |
| `{{ lead.phone('countryCallingCode') }}`           | `55`                                 |
| `{{ lead.phone('countryCode') }}`                  | `BR`                                 |
| `{{ lead.phone('numberType') }}`                   | `MOBILE`                             |
| `{{ lead.phone('uri') }}`                          | `tel:+5511912345678`                 |
| `{{ lead.maskedPhone('e164', '(999) 999-9999') }}` | Phone in the mask you define         |

In the mask, each `9` is a digit, filled in from left to right. For the Brazilian format, use `{{ lead.maskedPhone('national', '(99) 99999-9999') }}`.

### Location variables

| Variable                                   | What it returns                          |
| ------------------------------------------ | ---------------------------------------- |
| `{{ lead.location('last', 'city') }}`      | City of the last location                |
| `{{ lead.location('last', 'region') }}`    | State or region of the last location     |
| `{{ lead.location('last', 'country') }}`   | Country of the last location             |
| `{{ lead.location('last', 'timezone') }}`  | Time zone of the last location           |
| `{{ lead.location('last', 'latitude') }}`  | Latitude of the city center              |
| `{{ lead.location('last', 'longitude') }}` | Longitude of the city center             |

Replace `last` with `first` for the first location. The location is always the city.

### Opt-out variables

| Variable                                         | What it returns                                              |
| ------------------------------------------------ | ------------------------------------------------------------ |
| `{{ lead.unsubscribed() }}`                      | `true` if the person unsubscribed from all communications    |
| `{{ lead.unsubscribedFromChannel('email') }}`    | `true` if they unsubscribed from email                       |
| `{{ lead.unsubscribedFromChannel('whatsapp') }}` | `true` if they unsubscribed from WhatsApp                    |

### Your account's variables

The editor builds these variables from your custom fields, KPIs and tags. In the suggestion list, they show up with the name you gave them.

| Variable                                                              | For                                                                                  |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `{{ lead.field('Field name') }}`                                      | String, Number, Boolean and Date fields                                              |
| `{{ lead.dateField('Field name', 'YYYY-MM-DD') }}`                    | Date field, in the format you choose                                                 |
| `{{ lead.ageField('Field name') }}`                                   | Age in years, from a Date field                                                      |
| `{{ lead.locationField('Field name', 'city') }}`                      | Location field (also `region`, `country`, `timezone`, `latitude`, `longitude`)       |
| `{{ lead.phoneField('Field name', 'e164') }}`                         | Phone field (also the formats in the phone table)                                    |
| `{{ lead.maskedPhoneField('Field name', 'e164', '(999) 999-9999') }}` | Phone field with a mask                                                              |
| `{{ lead.maskedNumberField('Field name', '999 999 999 99') }}`        | Number field with a mask                                                             |
| `{{ lead.kpi('KPI name') }}`                                          | Value of a KPI                                                                       |
| `{{ lead.hasTag('Tag name') }}`                                       | `true` if the contact has the tag                                                    |

## Ready to copy

### Send every contact to a CRM

Agência Pulso sends every person who receives the welcome campaign to its CRM. Adjust the key names to what your CRM expects.

<Copy label="Headers" code>
{
  "Content-Type": "application/json",
  "Authorization": "Bearer YOUR_CRM_TOKEN"
}
</Copy>

<Copy label="Body Template" code>
{
  "nome": "{{ lead.name() }}",
  "email": "{{ lead.email() }}",
  "telefone": "{{ lead.phone('e164') }}",
  "cidade": "{{ lead.location('last', 'city') }}",
  "estado": "{{ lead.location('last', 'region') }}",
  "origem": "bip",
  "id_bip": "{{ lead.id() }}",
  "criado_em": "{{ lead.createdAt('YYYY-MM-DD') }}"
}
</Copy>

| Setting  | Value                                                    |
| -------- | -------------------------------------------------------- |
| Method   | POST                                                     |
| URL      | Your CRM's contact-creation endpoint                     |
| Campaign | **Email** channel, with or without an **Email Template** |
| Webhooks | This webhook (up to 3 per campaign)                      |

To send each person to the CRM the moment they sign up, with no email: create a campaign with only this webhook, set to **API Trigger** in **Immediate** mode, and trigger it through the [API](/en-us/api) whenever someone signs up.

### Notify your team

Many chat and automation tools take messages through an incoming webhook, with the text in the `text` field. Imobiliária Marés alerts its agents about every person who enters the launch campaign.

<Copy label="Headers" code>
{
  "Content-Type": "application/json"
}
</Copy>

<Copy label="Body Template" code>
{
  "text": "New lead: {{ lead.name() }} | {{ lead.email() || 'no email' }} | {{ lead.phone('international') || 'no phone' }} | {{ lead.location('last', 'city') || 'city not provided' }}"
}
</Copy>

Check which text field your destination tool expects and replace `text` if needed.

## How to measure

- **Webhook Stats**: in the **Webhooks** list, open the webhook and click **View Stats**. You see **Total Requests**, **Successful**, **Failed**, **Avg Response Time**, **Success Rate**, **Retry Rate**, **Error Rate**, **Status Codes** and the **Delivery Breakdown**. The numbers update **Live**.
- **Delivery Logs**: filter by **From Date**, **To Date** and **Limit**. Each row shows **Date**, **Status**, **URL**, **Method** and **Attempts**; under **View**, you see the full **Request** and **Response**.
- **In the campaign**: in **View Stats**, **Webhook Delivery** and **Webhook Errors** show the campaign totals, and **Recipient Interactions** shows the webhook for each contact.
- A high **Retry Rate** means the destination is slow or unstable. Open the **Response** in the logs to see why.

## Frequently asked questions

### What counts as a successful delivery?

A response with a 2xx code. Any other code, or no response, triggers a retry 60 seconds later, up to 6 attempts in total. After the sixth failure, the campaign logs the error for that contact.

### Does a webhook count as a send?

In an email campaign with webhooks, the send for that contact counts once: webhooks don't add to it. In a webhook-only campaign, each processed contact counts as one send, just like an email. **Test Webhook** doesn't count.

### Can I use variables in the URL or the headers?

No. The URL and the headers are fixed. Contact variables work in the **Body Template**.

### Does the test reach my system?

Yes. **Test Webhook** makes a real request, with a sample contact. Use a test environment or delete the record afterwards.

## See also

- [Campaigns: one-time, recurring and API-triggered](/en-us/campaigns)
- [BIP API: keys, contacts, events and triggers](/en-us/api)
- [Site script: page views and clicks tied to the contact](/en-us/site-script)
- [Compliance: preferences portal, opt-out and topics](/en-us/compliance)
- [Full API reference](https://bipmarketing.readme.io)
