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.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: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 is the exception: the sender is not a campaign lead, so it carries 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.
Extra fields
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
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.