2026-11-05 — Messaging leads added by profile URL
POST /v1/messages and POST /v1/messages/schedule returned 422 No conversation exists with this lead yet for leads
you already had a conversation with, if they were added with a linkedin.com/in/name URL and had never been through a
campaign. Botdog stored the URL but not the lead’s LinkedIn member id, and without one it never looked for the
conversation.
- Fixed when a lead has no member id, these endpoints now find it from the lead’s LinkedIn profile, save it, then find the conversation and send. You don’t need to re-add these leads. You still get 422 when there genuinely is no conversation; these endpoints never start one.
- Fixed a lead added with a Sales Navigator id (
ACwAA…) is resolved the same way for sending. The stored id does not change, so with the default inbox setting these conversations may still not appear in the Botdog inbox. - Changed this lookup is limited to 10 a minute and 100 a day per sending account, separately from your send limits.
Going over returns 429 with
retryAfter. - Changed if your integration treats this 422 as “not connected yet”, check it before this release reaches you: these calls can now send a real message into the existing conversation.
POST /v1/conversations/{id}/reply still
returns 404 for a lead with no member id until a send has found it.
No field was removed or renamed.
2026-10-22 — Team campaigns: whose LinkedIn account did the work
- Added
senderUserId,senderUserEmailandsenderUserLinkedinPublicUrlto every lead webhook event. In a team campaign they name the teammate whose LinkedIn account sent the invitation or message, or received the acceptance or reply, whilebotdogUser*stays the teammate who created the campaign. For every other campaign both name the same user. See Whose LinkedIn account did it. - Added
senderUserIdto each lead returned byGET /v1/leads,GET /v1/leads/{id}and the campaign leads endpoint, and to each event in a lead’sevents. For a team-campaign lead it is the teammate the lead is assigned to (nulluntil one picks it up); otherwise the lead’s owner. - Changed team campaign events now also reach 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 still receives an event once. Other campaigns are routed exactly as before.
2026-10-16 — Webhook for messages sent from the inbox, one reply per webhook
- Added the
INBOX_MESSAGE_SENTwebhook event: sent when you send a message by hand from the Botdog inbox, to anyone, campaign leads included. LikeNON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVEDit is delivered only to webhooks with scopeCONNECTED_ACCOUNT. SeeINBOX_MESSAGE_SENT. - Clarified
LEAD_MESSAGE_SENTonly fires for messages sent by a campaign step. Its behaviour has not changed; the two events never overlap. - Fixed a reply from someone who is a lead in several of your campaigns was delivered to each webhook once per
campaign — the same reply two or three times, differing only in
campaignId. Each webhook now receives it once: an all-campaigns webhook for the campaign whose conversation the reply arrived in, a specific-campaigns webhook for one of the campaigns it selected. A specific-campaigns webhook still always receives a campaign it subscribed to. The payload is unchanged, and replies from someone in a single campaign are delivered exactly as before. SeeLEAD_MESSAGE_REPLIED.
2026-10-15 — Lead dates in the list response, per-endpoint rate limits
- Added
invitedAt,connectedAt,repliedAt,withdrawnAtandstoppedAtto each lead returned byGET /v1/leads. They hold the same values asGET /v1/leads/{id}and arenulluntil the event happens, so you can follow a campaign’s progress from the list alone instead of reading each lead. - Changed rate limits are counted separately for each endpoint. Calls to one endpoint no longer count toward
another’s limit; for example,
GET /v1/leads/{id}calls no longer reduce what you can send toGET /v1/leads. The limits themselves are unchanged. See Rate limiting.
2026-10-01 — Replying to leads who wrote first
POST /v1/messages and POST /v1/conversations/{id}/reply could refuse a conversation that the Botdog inbox showed:
422 No conversation exists with this lead yet and 404 Conversation not found. It happened whenever Botdog had not
recorded the conversation against the lead — most often a lead who accepted your invitation and wrote to you before
your campaign messaged them.
- Fixed
POST /v1/messagesandPOST /v1/messages/schedule: when Botdog has no conversation recorded for the lead, it now finds the lead’s LinkedIn conversation, records it and sends. You still get 422 when there genuinely is no conversation yet — these endpoints never start one. - Fixed
POST /v1/conversations/{id}/replyandPOST /v1/conversations/{id}/read: the conversation id from the Botdog inbox URL now works even if it was never recorded against the lead, as long as the conversation is with one of your team’s leads. - Fixed new replies and first campaign messages now record their conversation against the lead straight away.
- Added
chatIdtoLEAD_MESSAGE_REPLIEDwebhooks: the conversation the reply arrived in, forPOST /v1/conversations/{id}/reply. That works for one-to-one LinkedIn, Sales Navigator and Recruiter conversations; InMail and group conversations cannot be replied to by id. SeeLEAD_MESSAGE_REPLIED. - Added
chatIdtoGET /v1/leads/{id}: the lead’s standard LinkedIn conversation,nulluntil Botdog has linked one. - Changed
POST /v1/messagesandPOST /v1/messages/schedulereturn 409 (account not connected) rather than 422 when the lead has no recorded conversation and the sending LinkedIn account is disconnected, since Botdog now needs the account to look the conversation up. - Fixed the docs said API keys are prefixed
bd_live_. They are not, and never were: a key is a 32-character hexadecimal string. Existing keys are unaffected and work for every endpoint, messaging included. - Changed the MCP tool
get_api_setup_guidereturnsapiKeyFormatin place ofapiKeyPrefix, which always held the wrongbd_live_value.
DELETE /v1/leads/{id}) does not affect its conversation; you can still message a stopped lead.
No REST endpoint or webhook field was removed or renamed.
2026-09-23 — Job title, location and 19 more webhook fields
Lead webhook events now carry everything Botdog holds about the person and about the lead row you imported, rather than just name, company, emails and phones. See Shared fields.- Added profile fields:
contactJobTitle,contactHeadline,contactLocation,contactProfilePictureUrl,contactLinkedinPublicProfileUrl,contactConnectionDegree,contactFollowerCount,contactIsPremium,contactIsOpenToWork,contactIsCreator,contactWebsites. - Added enrichment fields:
contactEnrichedEmail,contactEnrichedPhone. These are deliberately separate fromcontactEmails/contactPhones, which keep their original LinkedIn-only meaning. - Added lead fields:
leadCustomField1..3,leadCustomAttributes(every extra column from your CSV, as a flat string map keyed by your own column names),leadListId,leadListName,leadInvitedAt,leadConnectedAt. - Applies to
LEAD_PROFILE_VISITED,LEAD_INVITATION_SENT,LEAD_INVITATION_ACCEPTED,LEAD_MESSAGE_SENTandLEAD_MESSAGE_REPLIED.NON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVEDis unchanged: the sender is not a lead, so these fields are absent rather than empty. - Strings are
""when unknown; numbers and booleans arenullwhen unknown, sofalseand0stay meaningful. - Fixed test events sending
eventNamewhere real deliveries sendhumanReadableEventName. A mapping built from the Send test event button left that field blank on the first real delivery. Test events now sendhumanReadableEventNameand keepeventName, and carry all the new fields too. If you built an integration from a test event before today, re-check it against a real delivery. contactConnectionDegreeis1,2,3, or4for out of network, andnullwhen Botdog has not observed the distance. It is never0.- On
LEAD_INVITATION_SENTonly,leadInvitedAtis""— the event fires just before Botdog records the invitation against the lead. UsesentAton that event. - Fixed
contactIsOpenToWorknever being recorded at all. It is now stored when a Sales Navigator step fetches the profile, and staysnull— meaning “not checked” — otherwise.
2026-09-22 — Webhook contact identity fields
Every lead webhook event now identifies the person three ways, so integrations can match on what they imported and still see what LinkedIn shows. See Shared fields.- Added
leadNameandleadLinkedinUrl: the name and LinkedIn URL exactly as you imported them (viaPOST /v1/leads, CSV or URL import), never rewritten after import. Empty strings when no imported lead is behind the event — notably aNON_CAMPAIGN_LEAD_INBOX_MESSAGE_RECEIVEDfrom someone you never imported. - Added
contactLinkedinUrn: LinkedIn’s internal member id (ACoAAA…), the only identifier that never changes. - Changed
contactLinkedinUrl: now prefers the public profile URL as last observed on LinkedIn, falling back to the imported slug and then the member-id form. Between 2026-09-20 and this release it preferred the imported slug; before that, three events sent the member-id form. If you matched oncontactLinkedinUrl, switch toleadLinkedinUrl— it always equals your own record. contactNamenow falls back to the imported name instead of"undefined undefined"when Botdog has not yet fetched the profile, and is the LinkedIn display name on every event (the inbox event for imported contacts previously sent the imported name there).- Fixed
LEAD_INVITATION_ACCEPTEDandLEAD_MESSAGE_REPLIEDsendinghttps://www.linkedin.com/in/with an empty slug for leads imported by member id or Sales Navigator URL. - Test events from the dashboard now use the production field names (
contactCompanyNameand the three new fields).
Unreleased — expanded API surface
Not yet released. Adds, on top of v1.0:- Campaigns: create and update (name + dates), pause/resume, pause/resume specific activity types, list paused activities, list a campaign’s leads, archive, and delete.
- Leads: list with filters, full detail with event history, update metadata (notes / custom fields), find a lead by LinkedIn profile URL, and stop a sequence.
- Lists & blacklist: create, list, get, and add/remove members.
- Snippets: list, create, update, delete.
- Accounts: list connected accounts, read daily limits + remaining quota, and update limits (caps clamped to safe maximums).
- Webhooks: create, update, list, and get (delete is available via MCP).
- Messaging: send a message or reply, schedule a future send, and mark a conversation read/unread — guarded by a dedicated per-account send rate limit.
- MCP: expanded to 41 tools (23 read / 18 write).
stop_lead_in_campaignnow normalizeslinkedin_urlbefore matching, so a URL that differs only in spelling from the imported one no longer silently stops nothing. - New status codes in use:
202,409,502,503(see Errors). - Four new webhook event types are reserved but not yet delivered:
LEAD_INVITATION_WITHDRAWN,LEAD_SKIPPED,ACCOUNT_DISCONNECTED,DAILY_LIMIT_REACHED.
v1.0 — Initial release
Released.- Initial public API under
/v1. - Authentication via
x-api-key. - Campaigns, leads, users, teams, analytics, and health endpoints as documented in the OpenAPI spec.