ReactIn APIs

Webhook payloads and how to identify a lead

The same webhook event can come from a campaign, an automation or a lead magnet, and the payload differs. Learn how to tell the sources apart and which id identifies a person across all of them.

4 min read

Webhooks push an event to your endpoint every time something happens in your workspace: a lead is added, an invitation is sent, a connection is accepted, a first message goes out, a lead replies. Most of those events can be produced by more than one source, and the payload is not identical in every case. This page documents the differences, and in particular how to identify the lead.

Envelope

Every delivery has the same outer shape:

{
  "event": "CONNECTION_ACCEPTED",
  "timestamp": "2026-03-11T14:30:00.000Z",
  "data": { },
  "metadata": {
    "organizationId": "org_abc123",
    "campaignId": "campaign_xyz789",
    "listId": "list_xyz789",
    "linkedinAccountId": "li_acc_123",
    "automationId": "automation_xyz789"
  }
}

metadata only contains the keys that apply to the event. data is where the shape varies.

Identifying the source of an event

CONNECTION_ACCEPTED, INVITATION_SENT, FIRST_MESSAGE_SENT and LEAD_ADDED can each come from several places. Exactly one of these keys is present in data, and it tells you which:

Key in dataSource
campaignA campaign you started
listA SmartList
automationAn automation, for example auto-accept
leadMagnetA lead magnet

This matters for reporting. Auto-accept produces CONNECTION_ACCEPTED events for invitations that other people sent to you, not for invitations ReactIn sent on your behalf. If you compute an acceptance rate, filter on data.campaign first, otherwise inbound acceptances inflate your numerator and there is no matching INVITATION_SENT to divide by.

Identifying the lead

The data.lead object always carries two ids:

  • lead.leadId, the organization-wide lead id. It always means the same thing, whatever the source. This is the key to use when you deduplicate people, or match an event against a record you already stored.

  • lead.id, the most specific id available in that context. When data.campaign or data.list is present, it is the lead's list-scoped id, the same id returned by [Get leads from ReactIn via API] and the leadId accepted by [Update leads via API]. When data.automation or data.leadMagnet is present there is no list involved, so lead.id mirrors lead.leadId.

⚠️ The same person added to two SmartLists has two different lead.id values but a single lead.leadId. Use lead.leadId to reconcile a person across campaigns, automations and lead magnets.

Both keys are always present, and both can be null when the lead could not be resolved. Automation and lead magnet payloads also expose lead.linkedinUrl, which is a reliable fallback key.

CONNECTION_ACCEPTED, the three shapes

From a campaign. The lead comes from a SmartList, so the full lead record is available.

{
  "type": "CONNECTION_ACCEPTED",
  "conversation": null,
  "lead": {
    "id": "list_lead_XXX",
    "leadId": "lead_YYY",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "jane@example.com",
    "linkedinUrl": "https://linkedin.com/in/janesmith",
    "company": "Acme Inc",
    "headline": "Product Manager",
    "location": "New York, NY",
    "customFields": { "Is Matching My Target": true }
  },
  "campaign": { "id": "campaign_xyz789", "name": "Q1 Outreach" },
  "linkedinAccount": { "id": "li_acc_123", "name": "Sales Rep" }
}

From the auto-accept automation. Someone sent you an invitation and ReactIn accepted it. There is no list, no campaign and no enriched data, so the lead block is reduced to what LinkedIn returns on the invitation. lead.linkedinIdentifier is the LinkedIn member id.

{
  "type": "CONNECTION_ACCEPTED",
  "lead": {
    "id": "lead_YYY",
    "leadId": "lead_YYY",
    "linkedinIdentifier": "ACoAAABCDEFGHIJ",
    "firstName": "Jane",
    "lastName": "Smith",
    "linkedinUrl": "https://www.linkedin.com/in/janesmith",
    "headline": "Product Manager"
  },
  "automation": {
    "id": "automation_xyz789",
    "name": "[Automation - Auto accept] Sales Rep"
  },
  "linkedinAccount": { "id": "li_acc_123", "name": "Sales Rep" }
}

From a lead magnet. Same reduced lead block, with a leadMagnet block instead of automation.

{
  "type": "CONNECTION_ACCEPTED",
  "lead": {
    "id": "lead_YYY",
    "leadId": "lead_YYY",
    "firstName": "Jane",
    "lastName": "Smith",
    "linkedinUrl": "https://linkedin.com/in/janesmith",
    "headline": "Product Manager"
  },
  "leadMagnet": { "id": "lead_magnet_xyz789", "name": "LinkedIn Playbook" },
  "linkedinAccount": { "id": "li_acc_123", "name": "Sales Rep" }
}

INVITATION_SENT, FIRST_MESSAGE_SENT and LEAD_ADDED follow the same pattern: rich lead block from a campaign or a list, reduced lead block from an automation or a lead magnet.

Seeing the payloads in the app

Go to Settings → Webhooks, create or edit a webhook, and click View payload under any event. Events emitted by several sources show one tab per source.

Good practices

  • Branch on data.campaign / data.list / data.automation / data.leadMagnet rather than on the presence of a field inside lead. New optional fields may be added to payloads over time, so ignore keys you do not recognize instead of rejecting the delivery.

  • Deduplicate on lead.leadId, falling back to lead.linkedinUrl.

  • Webhooks only deliver events that occur after the webhook is created. Existing data does not trigger deliveries.