POST to your endpoint with a signed header.
Supported events
NON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVED and LEAD_MESSAGE_REPLIED never overlap. A reply from a lead one of your
campaigns has already messaged is always LEAD_MESSAGE_REPLIED; everyone else is the inbox event. Subscribe to both
to cover every inbound message without receiving anything twice.LEAD_MESSAGE_SENT and INBOX_MESSAGE_SENT never overlap either. A message a campaign step sends is always
LEAD_MESSAGE_SENT; a message you type yourself in the Botdog inbox is always INBOX_MESSAGE_SENT, even when the
recipient is a campaign lead. Messages sent from LinkedIn directly (the LinkedIn website or app) produce neither.Not yet delivered. The subscription API also accepts these event types, but Botdog does not deliver them yet —
subscribing to them today will not produce any calls to your endpoint:
LEAD_INVITATION_WITHDRAWN, LEAD_SKIPPED,
ACCOUNT_DISCONNECTED, DAILY_LIMIT_REACHED. They are reserved for an upcoming release; this page will be updated
with their payloads when delivery ships.Shared fields
Every payload includes the following base fields:Whose LinkedIn account did it (team campaigns)
Every lead event (LEAD_PROFILE_VISITED, LEAD_INVITATION_SENT, LEAD_INVITATION_ACCEPTED, LEAD_MESSAGE_SENT, LEAD_MESSAGE_REPLIED) also says whose LinkedIn account did the work: who sent the invitation or message, or received the acceptance or reply.
In a team campaign, contacts belong to the teammate who created the campaign (
botdogUser*), but each one is contacted from the LinkedIn account of the teammate it’s assigned to (sender*). Use the sender* fields to credit the right person in your CRM. For every other campaign, sender* and botdogUser* name the same user.
Which webhooks receive team campaign events. As well as the campaign creator’s and the team’s webhooks, an event
from a team campaign also reaches the all-campaigns webhooks of the teammate whose account did the work, and the
specific-campaigns webhooks any teammate created for that campaign. Each webhook receives an event once, however many
of these apply.
Who the event is about
All lead events (every event except those taggedCAMPAIGN_* in future versions) identify the person twice
over — once as you gave them to us, once as LinkedIn shows them today — plus one id that never changes.
Both versions are sent because they legitimately differ, and neither alone is enough: yours matches your records but
goes stale, LinkedIn’s is current but may no longer match anything you hold.
A worked example. You imported Teresa a year ago. Since then she has shortened her display name and changed her
custom URL. One webhook about her carries all of this at once:
Contact details, profile, and your imported lead
Lead events also carry how to reach the person, what LinkedIn says about them, and the lead row you imported:Empty values. String fields are
"" when Botdog does not know the value, never null — the same convention the
original contact fields already follow. Numbers and booleans are null when unknown, because 0 and false are real
answers here: contactIsPremium: false means “not Premium”, contactIsOpenToWork: null means “we have not been able
to check”.leadCustomAttributes is always a flat string map. Whatever extra columns your CSV had (crm_id, industry,
owner…) come back under those exact names, with every value as a string. Nested values are dropped rather than
serialised, so a field you map will never arrive as [object Object]. If you store your own CRM record id in a
spreadsheet column at import time, it comes straight back here on every event for that lead — the most direct way to
match a webhook to a record without matching on names or URLs at all.contactEnrichedEmail is deliberately separate from contactEmails. contactEmails and contactPhones mean
exactly what they always have: contact details published on the LinkedIn profile. Email and phone numbers that
Botdog’s enrichment found come back in their own fields instead, so an integration that already trusts contactEmails
as LinkedIn-sourced keeps working unchanged. If you want both, read both.contactLinkedinPublicProfileUrl vs contactLinkedinUrl. contactLinkedinUrl is always present and is built by
Botdog from the best identifier it holds (see the note above). contactLinkedinPublicProfileUrl is the URL LinkedIn
itself returned on the profile, and is "" until a campaign step has fetched the profile. When both are filled they
normally agree.Profile fields need a profile fetch.
contactHeadline, contactLocation, contactProfilePictureUrl,
contactFollowerCount, contactIsPremium and contactIsCreator come from the person’s LinkedIn profile, which
Botdog reads when a campaign step visits, invites or messages them. In practice they are filled by the time an
invitation is accepted. A lead who connected organically with no campaign step ever touching their profile can still
arrive with these empty. contactJobTitle, contactLocation and the lead* fields fall back to what you imported,
so they are usually filled either way.leadLinkedinUrl (or leadName): those are exactly the
strings you sent to POST /v1/leads or uploaded in a CSV, and Botdog never rewrites them after import, so they always
equal your copy. Use contactLinkedinUrl to notice when someone has changed their custom LinkedIn URL since you
imported them — when it differs from leadLinkedinUrl, the person is the same and your copy is stale. Use
contactLinkedinUrn as the key you store.
How
contactLinkedinUrl is chosen. It is the public identifier Botdog most recently saw on the person’s LinkedIn
profile — refreshed every time a campaign step fetches the profile (visit, invitation, message). If Botdog has never
fetched the profile it falls back to the slug from the URL you imported, and if it holds nothing public at all — a
lead imported by member id or Sales Navigator URL that no step has touched yet, or a sender outside your campaigns
— it falls back to https://www.linkedin.com/in/<member id>. That URL opens the profile but will never match a
custom URL as text; that is what leadLinkedinUrl and contactLinkedinUrn are for.NON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVED and INBOX_MESSAGE_SENT are the exception: the other person need not be a
campaign lead, so they carry only the five identity fields above. contactEmails, contactPhones,
contactCompanyName and every field in the table below are absent entirely rather than empty, because Botdog
holds no enriched profile data for people outside your campaigns. When the sender was never imported, leadName and
leadLinkedinUrl are empty strings and contactLinkedinUrl is usually the member-id form — contactLinkedinUrn is
then the only reliable way to identify the person.LEAD_PROFILE_VISITED
Sent when Botdog visits a lead’s profile as part of a campaign step.
Extra fields
Example payload
LEAD_INVITATION_SENT
Sent when a connection invitation is sent to a lead.
Extra fields
On this event specifically,
leadInvitedAt is "": the event fires the moment the invitation leaves, just before
Botdog records it against the lead. Use sentAt here. Every later event for that lead carries leadInvitedAt
normally.LEAD_INVITATION_ACCEPTED
Sent when a lead accepts your connection invitation.
Extra fields
Example payload
LEAD_MESSAGE_SENT
Sent when an outbound message is delivered to a lead on LinkedIn.
Extra fields
Example payload
LEAD_MESSAGE_REPLIED
Sent when a lead replies to one of your messages.
A person in several of your campaigns. One reply produces one delivery per webhook, not one per campaign. A
webhook scoped to all campaigns receives it for the campaign whose conversation the reply arrived in (or, if Botdog
cannot tell, the campaign that last contacted them). A webhook scoped to specific campaigns receives it for one of the
campaigns it selected, so
campaignId is always a campaign that webhook subscribed to.
Or answer by lead:
POST /v1/messages with the leadId sends into the lead’s standard LinkedIn conversation.
Example payload
NON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVED
Sent when someone outside any campaign sends you an inbound LinkedIn message. That covers:
- people who message you having never been added to Botdog
- contacts you imported or added to a list, but never put in a campaign
- people enrolled in a campaign that has not messaged them yet
LEAD_MESSAGE_REPLIED — so you can subscribe to both without receiving anything twice. Your own outgoing messages never produce this event.
Identifying the sender. For the second and third cases there is an imported lead, so leadName and
leadLinkedinUrl are filled exactly as for campaign events. For the first case — someone you never imported — there is
nothing of yours to return: leadName and leadLinkedinUrl are empty strings, contactName is the LinkedIn display
name, contactLinkedinUrl is usually the https://www.linkedin.com/in/<member id> form, and contactLinkedinUrn is
the member id itself. Key on contactLinkedinUrn for these.
Extra fields
Example payload
INBOX_MESSAGE_SENT
Sent when you send a message by hand from the Botdog inbox — replying in a conversation, or starting a new one from
the inbox or a contact’s profile. It fires for every recipient, campaign leads included, and only once the message has
reached LinkedIn.
Messages sent by a campaign step are LEAD_MESSAGE_SENT instead, so you can subscribe to both without receiving
anything twice. Messages you send from LinkedIn itself (the website or the mobile app) are not seen by Botdog when you
send them and produce no event.
Identifying the recipient. If the conversation is linked to one of your leads, leadName and leadLinkedinUrl are
filled as for campaign events. Otherwise they are empty strings and the contact fields come from LinkedIn — key on
contactLinkedinUrn, as for NON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVED.
Extra fields
Example payload
Field stability
- Field additions are non-breaking. Botdog may add new fields to existing payloads as features ship. Treat unknown fields as forward-compatible and ignore them.
- Removals or renames are breaking and will be announced in the Changelog.
- Test event payloads sent from the dashboard’s Send test event action use the same shape as production deliveries, with stub contact data.