Skip to main content
All articles

CRM

Connecting Swimlane to your CRM with webhooks and the API

The webhook contract Swimlane sends, how to verify and retry it, and receiver patterns for HubSpot, Salesforce and Pipedrive via n8n, Make or your own code.

By Swimlane Updated September 14, 2026 9 min read

A webhook CRM integration is only as good as your knowledge of what the sender actually sends. Swimlane has no native HubSpot, Salesforce or Pipedrive sync. It gives you three signed contact webhooks, a public REST API, and works with n8n and Make. This guide documents the exact contract, then shows how to receive it in each CRM without duplicates.

What to sync, and what to leave in Swimlane

Decide this before writing any code. The LinkedIn conversation, its notes and its reminders stay in Swimlane, next to the inbox. Your CRM needs to know about a person, and later about a deal. Two mechanisms cover that:

  • Webhooks (push) tell you a contact was created, edited or deleted, within about a minute.
  • The API (pull) tells you everything else about that contact: headline, company, tags, pipeline stage.

Most robust integrations use both: the webhook as the trigger, the API as the source of detail.

The three webhook events

Swimlane sends exactly three events, plus a test.

  • contact.created: a contact is added to your workspace, whether imported, created from a conversation or added by hand.
  • contact.updated: a contact’s own fields change, through an edit in the app or when an import or conversation sync brings new values for an existing contact. It does not fire on bulk edits, LinkedIn profile refreshes, tags, lists, pipeline moves, messages, replies or connection changes.
  • contact.deleted: a contact is deleted individually. Bulk deletes send nothing.
  • test: sent when you create a webhook or press “Test Webhook”.

There is no event for a reply, a tag, a stage change or an accepted invitation, and a webhook cannot be filtered by list or tag: it receives every matching event in the workspace.

Two delivery facts shape your receiver:

  • Latency. Events are queued and a worker delivers them once a minute. Expect up to about a minute.
  • One delivery per member webhook. An event goes to every member of the organisation that owns the contact, and each member’s active webhooks get their own copy. If three teammates each point a webhook at the same URL, you receive three deliveries. Give the CRM webhook one owner.

The payload

Every delivery is a POST with a JSON body and these headers:

  • Content-Type: application/json
  • X-Webhook-Event: the event name
  • X-Webhook-Timestamp: an ISO 8601 date
  • X-Webhook-Signature: present only when the webhook has a secret
{
  "event": "contact.updated",
  "timestamp": "2026-09-14T09:12:03.412+00:00",
  "data": {
    "contactId": 4821,
    "uuid": "3f0c5b1e-8d2a-4c1e-9f5b-2a7d6e1c9b40",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": null,
    "linkedinId": "jane-doe-42",
    "changes": ["headline", "email"]
  }
}
  • uuid is the stable identifier, the one the public API uses. Key your CRM on it.
  • contactId is internal. Ignore it.
  • linkedinId is the public profile slug: build https://www.linkedin.com/in/<linkedinId>.
  • firstName, lastName and email can be null. Most LinkedIn contacts have no email.
  • changes exists only on contact.updated. It lists the names of the fields that were submitted, not old and new values, and it can name fields whose value did not change.

Not in the payload: company, headline, location, ICP level, tags, lists, pipeline stage, messages.

Setting up a webhook in Swimlane

Go to Settings > Webhooks and press “Add Webhook”.

  1. Webhook URL. An HTTPS endpoint you control, or the webhook URL of an n8n or Make workflow.
  2. Events. Tick at least one.
  3. Maximum Retries. From 1 to 5, default 1. It is the total number of attempts, so 1 means no retry at all. Use 5 for a CRM.
  4. Generate secret key. Off by default: tick it. The secret is shown once, so store it right away. Editing the webhook offers “Regenerate secret key”.

Creating the webhook immediately sends a test event. The list then offers “Test Webhook” and an on/off toggle. Swimlane shows no delivery history, so log what you receive on your side.

Verifying the signature

The signature is an HMAC-SHA256 of the exact request body, keyed with your secret, sent as a lowercase hex digest with no sha256= prefix. The timestamp sits inside the signed body, so you can also reject stale deliveries. Each retry is re-signed with a fresh timestamp, so legitimate retries still pass.

import crypto from 'node:crypto'
import express from 'express'

const app = express()

app.post('/swimlane', express.raw({type: 'application/json'}), async (req, res) => {
  const expected = crypto
    .createHmac('sha256', process.env.SWIMLANE_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex')
  const received = req.get('X-Webhook-Signature') ?? ''
  const valid =
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  if (!valid) return res.status(401).end()

  const event = JSON.parse(req.body.toString('utf8'))
  if (Date.now() - Date.parse(event.timestamp) > 5 * 60 * 1000) return res.status(401).end()
  if (event.event === 'test') return res.status(200).end()

  await saveForProcessing(event) // a queue or a table: the CRM call happens later
  res.status(202).end()
})

Verify on the raw bytes wherever your stack allows it. In n8n, where the Webhook node hands you parsed JSON, recompute the HMAC on JSON.stringify of the body: Swimlane signs compact JSON.stringify output, so it reproduces the same bytes. If a tool cannot check the signature at all, treat its webhook URL as a secret and read the contact back from the API before writing anything. A forged event then only makes you look up a contact that does not exist.

Swimlane publishes no list of sender IPs, so an IP allowlist is not an option. The signature is the control.

Retries, ordering and idempotency

  • Success is any 2xx answer within 30 seconds. Anything else, or a timeout, is a failed attempt.
  • Retries follow a 15 s, 30 s, 60 s, 120 s backoff, up to your “Maximum Retries”. Because the worker runs once a minute, real gaps round up to the minute. After the last attempt the delivery is marked failed and dropped.
  • Duplicates happen: a retry after your endpoint timed out mid-processing, or several members’ webhooks on the same URL. The body differs between attempts, so you cannot deduplicate on a hash of it.
  • Order is not guaranteed. Do not assume created arrives before updated.

So make every write idempotent:

  • contact.created: search the CRM on the Swimlane uuid, create only if nothing is found.
  • contact.updated: update if found, create if not. You may have missed the creation.
  • contact.deleted: archive or flag the CRM record. Do not hard-delete sales history because someone cleaned a list.

Enriching events with the public API

Create a token in Settings > API. The same page shows the API host and links to the Swagger docs. Send it as Authorization: Bearer wpa_…; each token gets 100 requests per minute.

  • GET /v1/contacts/:uuid returns the contact with headline, location, linkedinId, companyUuids, listUuids, tagIds and pipelineStageUuid.
  • GET /v1/companies/:uuid gives the company name for your CRM’s company field.
  • GET /v1/tags maps tag ids to names; GET /v1/pipelines and GET /v1/pipelines/:uuid/stages map stage uuids to names.

Pulling instead: sync on a tag

Because no webhook fires on tags, stages or replies, “send this person to the CRM” is best expressed as a tag that a scheduled workflow reads:

  1. Tag the contact “to-crm”, by hand from the conversation or with a campaign’s “Add tag” step under a “Replied to message” condition.
  2. Every 15 minutes, n8n or Make calls GET /v1/contacts?tag_id=<id>&per_page=100.
  3. Upsert each contact in the CRM.
  4. Only after the CRM call succeeds, call POST /v1/contacts/:uuid/tags with the tags to keep. It replaces the whole set, so send the current tagIds minus “to-crm”, plus “in-crm” if you use one.

The API also works the other way: when the CRM marks a deal won, POST /v1/pipelines/stages/:stageUuid/contacts puts the contact in your Done stage in Swimlane, on any pipeline the token’s owner can see. The flow that hands replies to your CRM with n8n builds this end to end, and the n8n setup guide and the Make setup guide cover the connection itself.

HubSpot

HubSpot’s own “When a webhook is received” workflow trigger is limited to Data Hub Professional and Enterprise, and it only enrols records that already exist, matched on a property that requires unique values. It can update contacts you have synced before; it cannot create new ones. For contact.created, go through n8n or Make, which both have HubSpot modules, or your own endpoint calling HubSpot’s API.

  • Create a custom property for the Swimlane uuid, with unique values required, and search on it before creating.
  • firstName → firstname, lastName → lastname.
  • email → email only when it is not null.
  • The LinkedIn URL goes into a custom property.
  • Headline and company name come from the API call, not the payload.

Salesforce

Salesforce does not accept an unauthenticated POST into its objects, and Swimlane sends no OAuth token. Either use middleware (n8n and Make both connect to Salesforce), or expose an Apex REST class through a public Salesforce Site that verifies the signature before writing.

  • Add a Swimlane_UUID__c text field marked External ID and unique on Lead or Contact, and upsert on it so duplicates cannot form.
  • Leads require LastName and Company, and the payload has neither guaranteed. Fall back to the linkedinId for the name and fetch the company from the API, with a placeholder if none is known.

Pipedrive

Pipedrive’s webhooks go out of Pipedrive; there is nothing inside it to receive Swimlane’s calls. Use n8n, Make or your own endpoint calling the Pipedrive API.

  • Store the uuid in a text custom field on the person and search persons on it before creating.
  • A person needs a name: join first and last name, or fall back to the linkedinId.
  • Link the organisation from the company name returned by the API.
  • Create deals from the tag or stage workflow above, not on contact.created, or every imported commenter becomes a deal.

Testing checklist

  • The test event gets a 2xx and never touches the CRM.
  • A wrong secret gets a 401.
  • The same event delivered twice leaves one CRM record.
  • A contact.updated for an unknown uuid creates the record.
  • A contact.deleted archives rather than deletes.
  • With “Maximum Retries” at 5, an endpoint that is down for a minute still receives the event.

The team and API page summarises everything the workspace exposes to other tools.

Do this with Swimlane

Everything in this article, minus the manual part.

Start my free trial

See how it works in Swimlane Team & API

Keep reading

CRM 6 min read

Asking your CRM instead of clicking through it

An AI assistant with read access answers questions no dashboard was built for. One with write access needs a rule about when it stops. Here is what that looks like in practice.

Read →