Zapier
Pipe Warm identifications into any of Zapier’s 5,000+ apps. Every time Warm identifies a visitor on your site, Zapier can create a Salesforce lead, post to Microsoft Teams, add a Google Sheets row, or run any other action you can build a Zap for.
This works today with no extra setup on our side. Warm already sends a signed visitor_identified webhook to any URL you configure, and Zapier’s built in Webhooks by Zapier trigger accepts it directly.
There is no Warm app to install from the Zapier directory yet. You use Webhooks by Zapier as the trigger instead, which gives you the same result and works with every Zapier plan that includes Premium apps.
Before you start
You need:
- A Warm account with at least one API key (dashboard → Settings → API Keys).
- A Zapier plan that includes Webhooks by Zapier (a Premium app on Zapier’s side).
- At least one tracked website. Identifications are what trigger the webhook, so an unverified or untracked site will never fire one.
Step 1. Create the Zap and copy the hook URL
- In Zapier, click Create and then Zaps.
- For the trigger app, search for and choose Webhooks by Zapier.
- Choose the trigger event:
- Catch Hook for most Zaps. Zapier parses the JSON for you and every field is available in the mapping UI.
- Catch Raw Hook only if you plan to verify our HMAC signature yourself. See Securing the webhook below for why this matters.
- Leave Pick off a Child Key blank. You want the whole payload.
- Zapier shows you a Custom Webhook URL that looks like
https://hooks.zapier.com/hooks/catch/1234567/abcdef/. Copy it.
Step 2. Point Warm at the hook URL
- Open the dashboard and go to Settings → API Keys.
- Click the API key that belongs to the website you want events from.
- Find the Webhook panel.
- Click Set webhook (or Edit URL if you already have one configured) and paste the Zapier hook URL.
- Save.
When you set a webhook URL for the first time, Warm mints a signing secret and shows it to you once. Copy it somewhere safe even if you do not plan to verify signatures straight away. There is no way to read it back later, only to rotate it.
One API key has one webhook URL. If that URL is already pointed at your own backend, sending it to Zapier instead will stop your existing integration receiving events. Create a second API key for the Zapier destination, or fan out from your own backend.
Step 3. Send a test event
- Back in Zapier, click Test trigger. Zapier will sit and wait for a request.
- In the Warm dashboard, click Send test in the Webhook panel.
- Zapier picks up the synthetic
visitor_identifiedevent within a second or two and shows you the parsed fields.
The Warm dashboard shows the result of the test right below the button, plus a Recent Deliveries list with the HTTP status of the last few attempts. If Zapier does not see anything, that panel will tell you what went wrong.
Now map the fields you care about into your action step and turn the Zap on.
The payload
Every delivery is a POST with Content-Type: application/json and this envelope:
{
"event": "visitor_identified",
"timestamp": "2026-08-26T09:14:02.118Z",
"data": { }
}Headers on every request:
| Header | Value |
|---|---|
X-Warm-Event | The event type, currently visitor_identified or visitor_qualified |
X-Warm-Timestamp | Unix epoch milliseconds, the same instant as timestamp |
X-Warm-Signature | HMAC-SHA256 signature. Only present when a signing secret is set |
Company-level identification
This is the common case. Warm resolved the visitor’s IP to a company.
{
"event": "visitor_identified",
"timestamp": "2026-08-26T09:14:02.118Z",
"data": {
"identified": true,
"ip_address": "203.0.113.24",
"session_id": "0f3c9a4e-1d2b-4c6f-9a10-5b7e8c9d0e1f",
"identification_id": "7b2e1c05-8a4d-4f19-bb63-2c7d9e0a1f34",
"tracking_domain": "example.com",
"tracking_website_id": "c1d2e3f4-a5b6-4788-9900-112233445566",
"high_intent": true,
"identification_type": "company",
"identified_at": "2026-08-26T09:14:01.902Z",
"company": "Example Ltd",
"domain": "example.com",
"traffic_type": "business",
"confidence": 1,
"confidence_level": "confirmed",
"individual": null,
"company_data": {
"name": "Example Ltd",
"domain": "example.com",
"industry": "Software",
"employee_count": 120,
"location": "London, United Kingdom",
"linkedin_url": "https://www.linkedin.com/company/example"
},
"decision_makers": [
{
"name": "Alex Doe",
"title": "Head of Revenue Operations",
"linkedin_url": "https://www.linkedin.com/in/example",
"seniority": "director"
}
],
"session": {
"started_at": "2026-08-26T09:11:40.000Z",
"ended_at": null,
"duration_seconds": 142,
"page_count": 3,
"referrer": "https://www.google.com/",
"pages": [
{ "url": "https://example.com/pricing", "path": "/pricing", "title": "Pricing", "scroll_depth": 82, "duration_seconds": 61 }
]
}
}
}Person-level identification
US visitors matched through our person-level provider arrive in the same envelope with identification_type set to individual. The individual object is populated and a few company-level fields are null because the provider does not supply them.
{
"event": "visitor_identified",
"timestamp": "2026-08-26T09:20:11.004Z",
"data": {
"identified": true,
"ip_address": null,
"session_id": "2a1b3c4d-5e6f-4071-8293-a4b5c6d7e8f9",
"identification_id": "9e8d7c6b-5a49-4382-b1c0-d2e3f4a5b6c7",
"tracking_domain": "example.com",
"tracking_website_id": "c1d2e3f4-a5b6-4788-9900-112233445566",
"identification_type": "individual",
"identified_at": "2026-08-26T09:20:10.771Z",
"company": "Example Inc",
"domain": "example-inc.com",
"traffic_type": "business",
"confidence": 1,
"confidence_level": "confirmed",
"source": "rb2b_oem",
"individual": {
"linkedin_name": "Jordan Rivera",
"linkedin_title": "VP Marketing",
"linkedin_company": "Example Inc",
"linkedin_url": "https://www.linkedin.com/in/example",
"business_email": "jordan@example-inc.com"
},
"company_data": {
"name": "Example Inc",
"domain": "example-inc.com",
"industry": "Retail",
"employee_count": "50-200",
"location": "Austin, TX, 78701",
"linkedin_url": null,
"revenue_estimate": "$10M-$50M"
},
"session": {
"started_at": "2026-08-26T09:20:10.771Z",
"referrer": "https://www.linkedin.com/",
"captured_url": "https://example.com/demo"
}
}
}Person-level events do not carry high_intent, decision_makers, or a session.pages array. If your Zap reads those fields, add a Zapier filter on identification_type so the two shapes do not collide. In Zapier’s mapping UI a missing field simply comes through blank.
Fields worth filtering on
| Field | Notes |
|---|---|
data.identification_type | company or individual. The cleanest way to split the two payload shapes |
data.high_intent | Present on company-level events. Our single canonical high-intent flag, so you do not have to recompute it from page counts |
data.domain | The identified company’s domain. Best key for matching against a CRM |
data.company | Display name. Can be null when we only resolved a domain |
data.confidence_level | Currently always confirmed on delivered events |
data.identification_id | Stable ID for this identification. Use it to deduplicate |
data.session.pages | Which pages they viewed. Useful for routing pricing-page visitors differently |
Securing the webhook
A Zapier Catch Hook URL is unguessable but public. Anyone who learns the URL can post to it. Pick one of the two options below.
Option A: shared secret header (recommended for Zapier)
Warm can send an arbitrary header on every outbound event, which is the simplest thing for Zapier to check.
- In the dashboard, under the webhook panel, open Custom auth header (optional).
- Set a Header name such as
x-trigger-secretand a Header value of your own choosing. Both fields must be filled in. Either one alone does nothing. - Save.
- In Zapier, add a Filter by Zapier step immediately after the trigger and only continue when that header equals your value.
To see the header in Zapier’s mapping UI, use the Catch Raw Hook trigger, which exposes request headers. Plain Catch Hook gives you the parsed body only.
Option B: verify the HMAC signature
Every delivery is signed when a signing secret is set. The signature is computed over the string {timestamp}.{raw_body} where timestamp is the value of the X-Warm-Timestamp header and raw_body is the exact bytes of the request body.
Use the Catch Raw Hook trigger so you get the unmodified body, then add a Code by Zapier step running JavaScript:
const crypto = require('crypto');
// inputData wiring in the Code step:
// rawBody -> the raw request body from Catch Raw Hook
// timestamp -> the X-Warm-Timestamp header
// signature -> the X-Warm-Signature header
// secret -> your Warm signing secret (store it as a Zapier env var if you prefer)
const { rawBody, timestamp, signature, secret } = inputData;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature || '', 'utf8');
const valid = a.length === b.length && crypto.timingSafeEqual(a, b);
// Reject anything older than five minutes so a captured request cannot be replayed.
const ageMs = Date.now() - Number(timestamp);
const fresh = Number.isFinite(ageMs) && ageMs >= 0 && ageMs < 5 * 60 * 1000;
output = [{ valid: valid && fresh }];Follow it with a Filter by Zapier step that only continues when valid is true.
Signature verification only works against the raw body. If you re-serialise the parsed JSON the byte order and whitespace will differ and every signature will fail. This is the reason Option A is the easier path on Zapier.
Deduplication
Warm deduplicates before sending. Each identification produces one delivery, keyed on the identification, so a returning visitor inside our 24 hour dedup window will not fire a second event.
If a Zap action is expensive or creates records, add a second layer of safety by keying on data.identification_id. Zapier’s Storage by Zapier or a lookup step in your destination app both work for this.
Delivery, retries and failures
- Warm attempts delivery immediately when the identification lands.
- A non-2xx response or a timeout is retried up to three times, at roughly 1 minute, 5 minutes and 30 minutes after the failure.
- The request timeout is 10 seconds. Zapier’s hook endpoint responds well inside that.
- After the third retry the event is marked failed and left alone. It is still visible in Recent Deliveries with the response body we got back, which is usually enough to tell you what the receiver rejected.
If events stop arriving, check in this order:
- Recent Deliveries in the dashboard. If attempts are listed and failing, the response body will say why.
- If no attempts are listed at all, the webhook URL is not set on the API key that owns that website, or there have been no identifications to send.
- Confirm the Zap is switched on. A paused Zap still accepts the request, so deliveries will look successful on our side while nothing runs.
Sample Zaps
New Warm identification to a Slack message
-
Trigger: Webhooks by Zapier, Catch Hook.
-
Filter (optional): only continue if
data.high_intentis true, so you are not paging the channel on every visit. -
Action: Slack, Send Channel Message.
-
Message text:
:fire: {{data__company}} ({{data__domain}}) just visited {{data__tracking_domain}} Pages: {{data__session__page_count}} over {{data__session__duration_seconds}}s Industry: {{data__company_data__industry}} | Size: {{data__company_data__employee_count}}
Warm also has a native Slack integration under Integrations in the dashboard, with digest scheduling and segment filters built in. Use the Zap route when you want Slack behaviour our native channel does not cover, such as posting into a different workspace per segment.
New Warm identification to a Google Sheets row
-
Trigger: Webhooks by Zapier, Catch Hook.
-
Action: Google Sheets, Create Spreadsheet Row.
-
Suggested columns and mappings:
Column Mapping Identified at data__identified_atCompany data__companyDomain data__domainIndustry data__company_data__industryEmployees data__company_data__employee_countLocation data__company_data__locationPages viewed data__session__page_countHigh intent data__high_intentIdentification ID data__identification_id
Keeping identification_id in the sheet gives you a dedup key if you ever need to reconcile.
New Warm identification to a HubSpot contact
Only person-level events carry an email address, so gate this one.
-
Trigger: Webhooks by Zapier, Catch Hook.
-
Filter: only continue if
data__identification_typeexactly matchesindividualanddata__individual__business_emailexists. -
Action: HubSpot, Create or Update Contact.
-
Mappings:
HubSpot property Mapping Email data__individual__business_emailCompany name data__individual__linkedin_companyJob title data__individual__linkedin_titleWebsite data__domainLead source a static value such as Warm AI
Warm also has a native HubSpot integration, available under Integrations in the dashboard. If HubSpot is your main CRM it is worth connecting that first and using a Zap only for behaviour the native connection does not cover, such as writing to a custom object.
Limits and things to know
- Triggers only. There is currently no way to send data back into Warm from a Zap.
- One webhook URL per API key. Use separate keys if you need to fan out to more than one destination.
- Company-level and person-level events share an envelope but not every field. Filter on
identification_type. - Rotating the signing secret takes effect immediately, so update your Zap’s stored secret at the same time if you use Option B.
- Clearing the webhook URL stops delivery straight away. Failed events already in the queue are not resumed.