Webhooks

Real-time event notifications for your integrations.

Last updated: May 7, 2026 · payload v2

Overview

LeadShark sends webhooks via HTTP POST with JSON payloads. All webhooks include security headers for verification.

Manage webhooks programmatically. You can create, list, update, rotate the secret, delete, and test webhooks via the public REST API — no dashboard required. See Webhooks API for the full reference (Pro and above; new_profile_visit and new_like remain Apex-only).
Email Captured

email.captured

Pro
Profile Visit

new.profile.visit

Apex
Post Like

new.like

Apex
Post Comment

new.comment

Pro

Authentication & Security

Security Headers

X-Webhook-SignatureHMAC-SHA256 signature
X-Webhook-EventEvent type
Content-Typeapplication/json

Signature Verification

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  return signature === expected;
}

Payload Schema (v2)

Back-compat

Every webhook body now ships a unified shape so a single handler can deal with new.profile.visit, new.like and new.comment without per-event branching. The unification is 100% additive — every legacy field you're consuming today is still emitted exactly as before.

Backwards-compat guarantee

  • Existing fields (commenter_id, reactor_id, post_id, post_url, visitor, profile_owner) keep firing on every payload — no consumer code change required.
  • v2 only adds fields (payload_version, data.lead.actor_id, data.post, data.reaction, and an explicit data.automation: null when there is no automation context).
  • Legacy fields will be supported for at least two full quarters after this release. We'll announce a removal date in Docs and via the changelog before retiring anything.

Common envelope

{
  "event": "new.like" | "new.comment" | "new.profile.visit" | "email.captured",
  "timestamp": "2026-05-07T12:38:50.258Z",
  "payload_version": 2,
  "data": {
    "lead": { /* always present, see below */ },
    "post": { "id": "urn:li:activity:...", "url": "https://www.linkedin.com/feed/update/..." },
    "automation": { "id": "uuid", "name": "..." } | null,
    /* event-specific blocks: comment, reaction, visitor, profile_owner */
    /* legacy fields (post_id, post_url, etc.) are still emitted alongside */
  }
}

Unified data.lead

FieldType
data.lead.actor_idstring
data.lead.namestring
data.lead.first_namestring
data.lead.titlestring
data.lead.linkedin_urlstring
data.lead.linkedin_usernamestring
data.lead.connection_statusstring
data.lead.commenter_idstring · legacy
data.lead.reactor_idstring · legacy
data.lead.reaction_typestring · legacy

Unified data.post · data.automation

FieldType
data.post.idstring
data.post.urlstring?
data.automationobject | null
data.post_idstring · legacy
data.post_urlstring? · legacy

Email Captured

Pro
Event: email.captured— When a lead provides their email

Payload

{
  "event": "email.captured",
  "timestamp": "2025-09-15T11:43:42.659+00:00",
  "data": {
    "lead": {
      "name": "John Doe",
      "title": "Senior Marketing Manager",
      "email": "john.doe@example.com",
      "linkedin_username": "john-doe-12345"
    },
    "automation": {
      "id": "58c132e6-648c-4079-8815-77a3b2ec1adc",
      "name": "Q4 Marketing Guide"
    }
  }
}

Fields

FieldType
eventstring
timestampstring
data.lead.emailstring
data.automation.idstring

Profile Visit

Apex
Event: new.profile.visit— When someone visits your profile

Payload

{
  "event": "new.profile.visit",
  "timestamp": "2026-05-07T11:37:21.709Z",
  "payload_version": 2,
  "data": {
    "lead": {
      "name": "Jane Smith",
      "first_name": "Jane",
      "title": "Product Manager at Innovation Labs",
      "linkedin_url": "https://linkedin.com/in/jane-smith",
      "linkedin_username": "jane-smith",
      "actor_id": "ACoAAEXAMPLE987654321"
    },
    "visit_timestamp": "2026-05-07T11:37:21.709Z",

    // ── Legacy fields (kept for back-compat) ──
    "visitor": {
      "id": "ACoAAEXAMPLE987654321",
      "name": "Jane Smith",
      "title": "Product Manager at Innovation Labs",
      "linkedin_url": "https://linkedin.com/in/jane-smith",
      "linkedin_username": "jane-smith"
    },
    "profile_owner": {
      "id": "ACoAAEXAMPLE123456789",
      "name": "Your Name",
      "linkedin_url": "https://linkedin.com/in/your-name"
    },
    "meta": { "source": "linkedin", "timestamp": "2026-05-07T11:37:21.709Z" }
  }
}

Profile visits do not carry network-distance data, so data.lead.connection_status is omitted on this event.

Post Like

Apex
Event: new.like— Requires "Require like" enabled on automation

Payload

{
  "event": "new.like",
  "timestamp": "2026-05-07T12:38:50.258Z",
  "payload_version": 2,
  "data": {
    "lead": {
      "name": "Michael Johnson",
      "first_name": "Michael",
      "title": "Sales Director",
      "linkedin_url": "https://linkedin.com/in/michael-johnson",
      "linkedin_username": "michael-johnson",
      "connection_status": "Connected",
      "actor_id": "ACoAAEXAMPLE111222333",

      // ── Legacy fields (kept for back-compat) ──
      "reactor_id": "ACoAAEXAMPLE111222333",
      "reaction_type": "like"
    },
    "post": {
      "id": "urn:li:activity:7372270383585984512",
      "url": "https://www.linkedin.com/feed/update/urn:li:activity:7372270383585984512"
    },
    "reaction": { "type": "like" },
    "automation": { "id": "uuid", "name": "Engagement Campaign" },

    // ── Legacy fields (kept for back-compat) ──
    "post_id": "urn:li:activity:7372270383585984512",
    "post_url": "https://www.linkedin.com/feed/update/urn:li:activity:7372270383585984512"
  }
}

Likes captured outside an automation context (i.e. on posts where you have not enabled "Require like" but have enrolled in like tracking) will arrive with data.automation: null.

Post Comment

Pro
Event: new.comment— When someone comments on your post

Payload

{
  "event": "new.comment",
  "timestamp": "2026-05-07T12:42:27.124Z",
  "payload_version": 2,
  "data": {
    "lead": {
      "name": "Sarah Wilson",
      "first_name": "Sarah",
      "title": "Content Marketing Specialist",
      "linkedin_url": "https://linkedin.com/in/sarah-wilson",
      "linkedin_username": "sarah-wilson",
      "connection_status": "2nd Connection",
      "actor_id": "ACoAAEXAMPLE444555666",

      // ── Legacy field (kept for back-compat) ──
      "commenter_id": "ACoAAEXAMPLE444555666"
    },
    "comment": {
      "id": "7371940875070795776",
      "text": "Great insights! Thanks for sharing.",
      "created_at": "2026-05-07T16:21:08.656Z"
    },
    "post": {
      "id": "urn:li:activity:7372270383585984512",
      "url": "https://www.linkedin.com/feed/update/urn:li:activity:7372270383585984512"
    },
    "automation": { "id": "uuid", "name": "Content Engagement" },

    // ── Legacy fields (kept for back-compat) ──
    "post_id": "urn:li:activity:7372270383585984512",
    "post_url": "https://www.linkedin.com/feed/update/urn:li:activity:7372270383585984512"
  }
}

Setup Guide

  1. Configure webhook endpoint in Dashboard Settings or via the REST API
  2. Select event types to receive
  3. Save your webhook secret for verification (returned only at creation / rotation)
  4. Ensure endpoint returns 2xx status
  5. Optional: send a test event from the dashboard or POST /api/v1/webhooks/:id/test

Important

  • • Webhooks timeout after 5-10 seconds
  • • Return 2xx status to acknowledge

Code Examples

Node.js / Express

const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(JSON.stringify(req.body)).digest('hex');

  if (sig !== expected) return res.status(401).json({ error: 'Invalid' });

  // v2 unified shape — same field names across like / comment / visit.
  // Falls back to legacy field names if a v1 payload arrives.
  const { event, data } = req.body;
  const actorId =
    data.lead?.actor_id ??
    data.lead?.commenter_id ?? // legacy: new.comment
    data.lead?.reactor_id ??   // legacy: new.like
    data.visitor?.id;          // legacy: new.profile.visit
  const postId = data.post?.id ?? data.post_id;

  switch (event) {
    case 'email.captured':
      console.log('Email captured:', data.lead.email);
      break;
    case 'new.comment':
      console.log('Comment from', actorId, 'on post', postId, ':', data.comment.text);
      break;
    case 'new.like':
      console.log(actorId, 'reacted', data.reaction?.type ?? data.lead.reaction_type, 'on', postId);
      break;
    case 'new.profile.visit':
      console.log('Profile visited by', actorId);
      break;
  }
  res.json({ received: true });
});

Python / Flask

from flask import Flask, request, jsonify
import hmac, hashlib, json, os

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    sig = request.headers.get('X-Webhook-Signature')
    secret = os.environ.get('WEBHOOK_SECRET')
    expected = hmac.new(secret.encode(),
        json.dumps(request.json, separators=(',',':')).encode(),
        hashlib.sha256).hexdigest()
    
    if not hmac.compare_digest(sig, expected):
        return jsonify({'error': 'Invalid'}), 401
    
    return jsonify({'received': True})

Need Help?

Contact our team for webhook support.

Contact