Swimlane
Zurück zum Hilfezentrum

Webhook payloads and signature verification

This page describes exactly what Swimlane sends to your webhook URL and how to check that a request really comes from Swimlane. To create a webhook, see Set up webhooks.

The request

Swimlane sends a POST with a JSON body and these headers:

  • Content-Type: application/json
  • X-Webhook-Event: the event name, for example contact.updated
  • X-Webhook-Timestamp: when this attempt was sent (ISO 8601)
  • X-Webhook-Signature: only when the webhook has a secret key

The body

{
  "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": ["note"]
  }
}
  • event: contact.created, contact.updated or contact.deleted.
  • data.uuid: the contact's id in the public API. Use it as the key in your CRM or database.
  • data.contactId: an internal number. Don't rely on it.
  • data.firstName, data.lastName, data.email: can be null. Most LinkedIn contacts have no email.
  • data.linkedinId: the LinkedIn identifier. The profile opens at https://www.linkedin.com/in/<linkedinId>.
  • data.changes: only on contact.updated. It lists the fields that were sent with the update. When the update comes from an import or the inbox sync, it can include fields whose value didn't change. Treat it as a hint, not as a precise diff. It never contains old values.

The payload doesn't include the company, headline, location, tags, lists, pipeline stage, messages or ICP fit. To get more, call the public API with the uuid:

curl "https://api.getswimlane.com/v1/contacts/3f0c5b1e-8d2a-4c1e-9f5b-2a7d6e1c9b40" \
  -H "Authorization: Bearer wpa_your_token_here"

For a contact.deleted event, this call answers 404: the contact is already gone. Keep what you need from the webhook body.

The test event

The test sent when you create a webhook, or with Test Webhook, looks like this:

{
  "event": "test",
  "timestamp": "2026-09-14T09:10:00.000+00:00",
  "data": { "message": "This is a test webhook from Swimlane" }
}

Filter it out (event equals test) so it doesn't create an empty record in your CRM.

Verify the signature

When the webhook has a secret key, X-Webhook-Signature is the HMAC-SHA256 of the exact request body, keyed with your secret and written as lowercase hexadecimal (no sha256= prefix). The timestamp is inside the signed body, and both are regenerated on each attempt.

To verify:

  1. Read the raw body, before any JSON parsing. Re-encoding parsed JSON can change the bytes and break the check.
  2. Compute the HMAC-SHA256 of that raw body with your secret, as hex.
  3. Compare it with the header using a constant-time comparison. Reject the request if they differ.

Node.js (Express):

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

const app = express()
const secret = process.env.SWIMLANE_WEBHOOK_SECRET

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

  const payload = JSON.parse(req.body.toString('utf8'))
  res.status(200).end()
  // then handle payload.event and payload.data.uuid
})

Python (Flask):

import hashlib, hmac, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["SWIMLANE_WEBHOOK_SECRET"].encode()

@app.post("/swimlane")
def swimlane():
    raw = request.get_data()
    expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    received = request.headers.get("X-Webhook-Signature", "")
    if not hmac.compare_digest(expected, received):
        abort(401)
    payload = request.get_json()
    return "", 200

Good to know

  • There is no delivery id. If you need to ignore duplicates, combine event, data.uuid and your own processing state.
  • Delivery order is not guaranteed across retries: a retried contact.updated can land after a newer one. When order matters, re-read the contact through the API.
  • If you lose the secret, edit the webhook and tick Regenerate secret key. Update your receiver right away: requests are then signed with the new secret.

War dieser Artikel hilfreich?