Offer 20% off your first 3 months on any plan.

0 Days
:
00 Hours
:
00 Mins
:
00 Secs
Claim 20% off →

Guide to Sbl APIs & Webhooks

Sbl.so API Reference

API Reference · v1

Build on Sbl.so

Sbl.so runs AI-driven outreach — LinkedIn and WhatsApp campaigns, replies, and lead qualification. The REST API lets you manage campaigns and contacts programmatically; webhooks push every campaign event to your own systems in real time.

Overview

How the API is organized

Every request goes to a single base URL and is authenticated with a bearer token. Responses are JSON. Endpoints are scoped to a company — most requests take a companyId, and Sbl.so checks that the campaign or contact you’re asking for actually belongs to that company before returning anything.

PropertyValue
Base URLhttps://api.sbl.so
FormatJSON request and response bodies
AuthBearer token in the Authorization header
Token lifetime30 days from issue
PaginationCursor-based — pass the last item’s id back as lastXId

Guide

Authentication

Every endpoint except token generation requires a bearer token. Fetch one with your account email and password, then attach it to every subsequent request.

POST/auth/token

Generate token

Authenticates a user with email and password and returns a bearer token valid for 30 days. If any other endpoint returns 401 Unauthorized, regenerate the token here and retry the request.

Request body

FieldTypeRequiredDescription
emailstringRequiredYour account email address
passwordstringRequiredYour account password

Request example

cURL
curl https://api.sbl.so/auth/token \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email": "[email protected]", "password": "your_password"}'

Response — 200 OK

JSON
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5..."
}

Errors

StatusDescription
400Missing or invalid parameters
401Invalid email or password
429Too many requests — check the Retry-After header and retry

Every authenticated request needs Authorization: Bearer <token> in its headers. Tokens are valid for 30 days — cache one and only re-request it when you hit a 401.

Guide

Errors & rate limits

Errors return a non-2xx status with details in the body. The status codes below recur across most endpoints; endpoint-specific cases are called out where they apply.

StatusMeaning
400Missing, invalid, or unknown fields in the request
401Token expired or invalid — regenerate it via /auth/token
403The campaign or company doesn’t belong to the authenticated user
404Campaign or resource not found
409A conflicting change — e.g. the record changed mid-update, or the user is already in the campaign
429Too many requests — read the Retry-After header and back off before retrying

Respect Retry-After. On a 429, the header tells you exactly how long to wait. Retrying immediately just extends the backoff.

Campaign endpoints

Campaigns

List, read, and update campaigns; manage the people inside them.

GET/campaigns

List campaigns

Returns a list of campaigns for a company.

Authentication

Authorization: Bearer <token>

Query parameters

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
statusesstring | string[]OptionalFilter by campaign status(es)
typesstring | string[]OptionalFilter by campaign type(s)
isArchivedbooleanOptionalFilter archived campaigns (default: false)
searchQuerystringOptionalSearch campaigns by name
lastCampaignIdstringOptionalPagination cursor — ID of the last campaign from the previous page

Response — 200 OK

JSON
[
  {
    "id": "123",
    "name": "My Campaign",
    "status": "running",
    "type": "outbound",
    "communicationChannelToUse": 1,
    "statistics": {},
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
]

Errors

StatusDescription
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
GET/campaigns/:campaignId

Get campaign

Returns full details for a single campaign.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Query parameters

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company

Response — 200 OK

JSON
{
  "id": "123",
  "name": "My Campaign",
  "status": "running",
  "type": "outbound",
  "objective": "...",
  "initialMessage": "Hello!",
  "communicationChannelToUse": 1,
  "statistics": {},
  "analytics": {},
  "batches": [],
  "createdAt": "2024-01-01T00:00:00.000Z"
}

Errors

StatusDescription
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
PATCH/campaigns/:campaignId

Update campaign

Updates a campaign. Send only the fields you want to change — everything else is left untouched, and unknown fields are rejected. Concurrency is handled for you: the API reads the campaign’s current revision before applying the update.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Request body

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
namestringOptionalCampaign name
objectivestringOptionalCampaign objective
targetUsersobjectOptional{ title, description } — the ICP
communicationChannelToUsenumberOptionalRequired when updating initialMessage or followupMessages
initialMessagestringOptionalFirst-touch message, validated per channel
initialMessageDelayInMsnumberOptionalDelay before the first message (max 90 days)
followupMessagesarrayOptional[{ id, message, followupAfterInMs, asVoiceNote? }]
smartFollowupsConfigobjectOptional{ delayInMs[], followupMessageReasoning[] } — equal-length arrays, min 5 minutes each
nonConnectedLeadsMessagestringOptionalMessage for leads who aren’t connections yet
sendInviteWithoutMessagebooleanOptionalSend LinkedIn invites without a note
productLinksarrayOptional[{ link, description }]
icpFilterobjectOptional{ criteria, requirements, networkDistances }. criteria and networkDistances are each optional but at least one is required; requirements is required alongside criteria. networkDistances: FIRST_DEGREE | SECOND_DEGREE | THIRD_DEGREE | OUT_OF_NETWORK
userTagsToRunOnarrayOptionalTag IDs to target
leadListSetsToRunOnarrayOptionalLead list set IDs to target
campaignsToRunOnarrayOptionalRetargeting source campaigns
commentToDMarrayOptional[{ postUrl, trigger }] — LinkedIn post URLs, must be unique
likeToDMarrayOptional[{ postUrl }] — LinkedIn post URLs, must be unique
pausedItemnumberOptionalPause a specific part of the campaign
isArchivedbooleanOptionalArchive or unarchive the campaign
webhooksEnabledbooleanOptionalToggle webhooks for this campaign
customAnalyticsFieldsarrayOptional[{ name, description }] — unique, JSON-key-safe names

Request example

JSON
{
  "companyId": "456",
  "name": "Q3 Founders Outreach",
  "objective": "Book demos with seed-stage founders",
  "communicationChannelToUse": 1,
  "initialMessage": "Hi {{name}}, saw you're building in devtools..."
}

Response — 200 OK

JSON
{
  "updated": true,
  "status": 200,
  "payload": {}
}

Errors

StatusDescription
400Invalid or unknown fields in the body
401Token expired or invalid
403Company does not belong to the logged-in user
404Campaign not found
409Campaign changed while the update was being applied
429Too many requests — check Retry-After header and retry
GET/campaigns/:campaignId/users

List campaign users

Returns a paginated list of users in a campaign.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Query parameters

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
userChatStatusnumberOptionalFilter by user chat status
searchQuerystringOptionalSearch users by name
lastUserIdstringOptionalPagination cursor — ID of the last user from the previous page

Response — 200 OK

JSON
[
  {
    "id": "456",
    "name": "John Doe",
    "phoneNumber": "+1234567890",
    "email": null,
    "linkedinChannelDetails": null,
    "tags": [],
    "aiChatOff": false,
    "status": 1,
    "lastMessageCreatedAt": "2024-01-01T00:00:00.000Z",
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
]

Errors

StatusDescription
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
GET/campaigns/:campaignId/users/:userId/messages

Get user conversation

Returns messages in a user’s conversation within a campaign, ordered oldest first.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign
userIdstringRequiredThe ID of the company user

Query parameters

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
lastMessageIdstringOptionalPagination cursor — ID of the last message from the previous page

Response — 200 OK

JSON
[
  {
    "id": "789",
    "senderType": "bot",
    "content": "Hello, welcome!",
    "type": "text",
    "status": 2,
    "communicationChannelUsed": 1,
    "trigger": "initial",
    "scheduledFor": null,
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
]

Errors

StatusDescription
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
GET/campaigns/:campaignId/human-intervention

List human intervention users

Returns users in the campaign that require human intervention.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Query parameters

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
lastUserIdstringOptionalPagination cursor — ID of the last user from the previous page

Response — 200 OK

JSON
[
  {
    "id": "456",
    "name": "John Doe",
    "phoneNumber": "+1234567890",
    "email": null,
    "linkedinChannelDetails": null,
    "tags": [],
    "aiChatOff": false,
    "status": 1,
    "lastMessageCreatedAt": "2024-01-01T00:00:00.000Z",
    "createdAt": "2024-01-01T00:00:00.000Z"
  }
]

Errors

StatusDescription
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
POST/campaigns/generate

Generate campaign

AI-generates a new campaign based on a natural language description.

Authentication

Authorization: Bearer <token>

Request body

NameTypeRequiredDescription
companyIdstringRequiredThe ID of the company
descriptionstringRequiredNatural language description of the campaign goal
communicationChannelnumberRequired1 = WhatsApp, 3 = LinkedIn

Request example

JSON
{
  "companyId": "123",
  "description": "Reach out to CTOs at fintech companies about our new product",
  "communicationChannel": 1
}

Response — 200 OK

JSON
{
  "id": "456",
  "pendingCommentToDM": false,
  "pendingMetaAdId": false,
  "pendingProductLinks": []
}

Errors

StatusDescription
400Missing or invalid parameters
401Token expired or invalid
403Company does not belong to the logged-in user
429Too many requests — check Retry-After header and retry
POST/campaigns/:campaignId/send-message

Send message in campaign

Sends a message to a specific user in a campaign.

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Request body

NameTypeRequiredDescription
userIdstringRequiredThe ID of the company user
messagestringRequiredThe message text to send

Request example

JSON
{
  "userId": "789",
  "message": "Hello, following up on our conversation!"
}

Response — 200 OK

JSON
{
  "messageId": "xxxx"
}

Errors

StatusDescription
400User not in campaign, campaign not running, channel not connected, or user in an invalid chat state
401Token expired or invalid — regenerate using /auth/token
403Campaign does not belong to the logged-in user
404Campaign not found
429Too many requests — check Retry-After header and retry
POST/campaigns/:campaignId/add-user

Add user to campaign

Adds a new user to a campaign. WhatsApp campaigns require phoneNumber; LinkedIn campaigns require linkedinProfileUrl. LinkedIn campaigns can also include customMessage for templates that use [custom message].

Authentication

Authorization: Bearer <token>

Path parameters

NameTypeRequiredDescription
campaignIdstringRequiredThe ID of the campaign

Request body

NameTypeRequiredDescription
namestringRequiredFull name of the user
phoneNumberstringOptionalRequired for WhatsApp campaigns
linkedinProfileUrlstringOptionalRequired for LinkedIn campaigns
customVariablesobjectOptionalKey-value pairs of custom data to attach to the user. Keys and values must be non-empty strings.
customMessagestringOptionalLinkedIn-only custom text used to replace [custom message] in campaign messages

Request example

JSON
{
  "name": "John Doe",
  "linkedinProfileUrl": "https://www.linkedin.com/in/johndoe",
  "customVariables": {
    "company": "Acme Inc",
    "role": "CTO"
  },
  "customMessage": "I noticed Acme recently expanded its engineering team."
}

Response — 200 OK

This endpoint returns an empty response body.

Errors

StatusDescription
400Campaign not in a valid state, contact field doesn’t match the campaign channel, or customMessage used outside a LinkedIn campaign
401Token expired or invalid — regenerate using /auth/token
403Campaign does not belong to the logged-in user
404Campaign not found
409User is already part of this campaign
429Too many requests — check Retry-After header and retry

Webhooks

Overview & setup

Webhooks send real-time campaign updates to the tools your team already uses. Instead of polling the API, register an endpoint once and Sbl.so pushes an event to it the moment something happens — a connection accepted, a reply, a lead that needs a human.

Setting one up

From API Integration → Webhooks → Add webhook, in the app:

StepWhat you set
1Endpoint URL — the HTTPS URL Sbl.so will POST each event to
2Events to subscribe to — pick one or more of the seven event types below
3Campaign filter — scope the webhook to all campaigns or a specific one

Every event is delivered as an HTTP POST with a JSON body. Every payload shares the same envelope — event, timestamp, campaign, prospect, and sender — plus fields specific to that event, shown per event below.

Webhooks

Events & payloads

Seven event types cover the lifecycle of an outbound touch, from the first connection request to a lead that needs a human. Subscribe to only what you plan to act on.

connection_request_sentLinkedIn invite sent
connection_request_acceptedInvite accepted
message_sentOutbound message sent
prospect_repliedProspect replied
message_failedOutbound message failed
lead_action_neededFlagged for a human
custom_dataAI-extracted fields
connection_request_sent

Fires when a LinkedIn connection request is sent.

JSON
{
  "event": "connection_request_sent",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  }
}
connection_request_accepted

Fires when the prospect accepts a LinkedIn connection request.

JSON
{
  "event": "connection_request_accepted",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  }
}
message_sent

Includes the outbound message text, or "Attachment message" for media.

Addsmessage
JSON
{
  "event": "message_sent",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  },
  "message": "Hi Lokesh, wanted to follow up on our conversation."
}
prospect_replied

Includes the prospect’s reply text, or "Attachment message" for media.

Addsmessage
JSON
{
  "event": "prospect_replied",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  },
  "message": "Thanks, can you share more details?"
}
message_failed

Includes the failed outbound message text when available.

Addsmessage
JSON
{
  "event": "message_failed",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  },
  "message": "Hi Lokesh, wanted to follow up on our conversation."
}
lead_action_needed

Fires when the lead is flagged for human intervention.

JSON
{
  "event": "lead_action_needed",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  }
}
custom_data

Includes permanent prospect fields plus any custom fields extracted by AI.

AddscustomData
JSON
{
  "event": "custom_data",
  "timestamp": "2026-06-10T00:00:00.000Z",
  "campaign": {
    "id": "483",
    "name": "Outbound Campaign"
  },
  "prospect": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "phone_number": "+919****9999",
    "email": "[email protected]"
  },
  "sender": {
    "name": "Founder LinkedIn Profile"
  },
  "customData": {
    "name": "Lokesh",
    "linkedin_profile": "https://www.linkedin.com/in/example",
    "email": "[email protected]",
    "phone_number": "+919****9999",
    "lead_score": 0.8,
    "attachments": {
      "images": true,
      "audio_messages": true
    }
  }
}

Webhooks

Testing a webhook

Before wiring an event into production, verify your endpoint actually responds. The Webhook test panel on the Webhooks tab sends one sample event of your choosing straight to an endpoint URL — no webhook is created, and nothing gets saved until you’re ready.

Good to know: the sample payloads above are exactly what a ping test sends, so what you see in your endpoint logs during a test is what production traffic looks like.

Webhooks

Native integrations

Point-and-click destinations for teams who’d rather not stand up their own endpoint. Each one subscribes to the same events above and maps them into the target tool.

HubSpot

CRM

Sync contacts, deals, and sequences — every signal and reply flows straight into your HubSpot pipeline, no manual logging.

Slack

Notifications

Get a Slack ping the moment a high-intent buyer engages. React fast, route to the right rep, never miss a hot lead.

Zapier

Automation

Connect Sbl.so to 6,000+ apps via Zapier. Trigger workflows on signal events, new replies, or booked meetings — no code needed.

n8n

Automation

Build advanced self-hosted automation flows with n8n. Full control over your data, logic, and integrations pipeline.

Notion

Workspace

Send outreach activity and signal summaries to Notion databases through Zapier. Keep your whole team aligned in one place.

Salesforce

CRM

Push intent signals and outreach activity into Salesforce objects in real time. Keep your reps working in the tool they know.

Pipedrive

CRM

Create deals and update stage automatically when a prospect replies or books. Your pipeline stays accurate without lifting a finger.

Make

Automation

Design visual automation scenarios in Make. Route signals, enrich leads, and update any tool in your stack automatically.

Zoho CRM

CRM

Send leads and campaign activity into Zoho CRM through Zoho Flow. Keep customer records current without manual updates.

Google Sheets

Workspace

Send webhook events into Google Sheets through Make. Build a live, shareable record of campaign activity.

Custom server

Webhook

Send events directly to your own webhook endpoint. Use your server to process, store, or route Sbl.so activity however you need.

Sbl.so API Reference · Base URL https://api.sbl.so Questions? Reach the team via Support in-app.
Scroll to Top
Exclusive: Top 1% onlyJoin our Community (for FREE)