BotPulsar

Home/ Developers/API reference

API reference

Every endpoint an API key can call

Scope, fields, a curl you can paste, and the response — for contacts, conversations, messages, templates, API campaigns, automations and webhooks. Base URL https://api.botpulsar.com/api/v1/.

Authenticate every request with Authorization: ApiKey smk_<prefix>_<secret> and X-Tenant-ID, then call the endpoints below. Each one lists the single scope it needs, so you can grant a key exactly what an integration uses. Every example here is also in the Postman collection, and the same surface is browsable as live OpenAPI.

Read this first

Conventions that apply everywhere

Base URL

https://api.botpulsar.com/api/v1/ — HTTPS only. The version is in the path; additive changes (new fields, new endpoints) ship inside v1, so ignore fields you do not recognise instead of failing on them.

Authentication

Authorization: ApiKey smk_<prefix>_<secret> plus X-Tenant-ID: <workspace id> on every request. The key acts as the person who created it, limited to the scopes you granted. The Simple send API (below) does not need X-Tenant-ID.

Content type

Send and receive application/json (UTF-8). Message, campaign and automation endpoints reject unknown fields with 400, so a typo fails loudly instead of being ignored.

Phone numbers

E.164 with a leading + and country code, e.g. +919812345678. No spaces, dashes or leading zeros. A number that cannot be parsed is refused with 400.

IDs and timestamps

Every id is a UUID. Every timestamp is ISO 8601 in UTC, e.g. 2026-10-11T08:30:00Z.

Pagination

List endpoints return {count, next, previous, results}, 50 per page. Follow next until it is null, or pass ?page=N.

Idempotency

Sending a message: pass your own client_message_id (a UUID) — retrying the exact same request returns the original message instead of sending twice. Simple send API: idempotency_key. Automation triggers: the Idempotency-Key header. Always set one when your code can retry.

Rate limits

Per key, per hour. Defaults: 300/hour on contacts, conversations, messages, campaigns, automations and webhook management; 20,000/hour on the Simple send API. Over the limit you get 429 with a Retry-After header. Need more? Ask us — limits are configurable per deployment.

Errors

{"error": {"code": "...", "details": ...}} with a matching HTTP status. Branch on error.code, not on the message text. Full list on the Errors page.

The 24-hour window

WhatsApp only allows free-form text within 24 hours of the customer's last message. Outside it you must send an approved template — otherwise the API answers 409 whatsapp_template_required. Read messaging_window on a conversation to check before you send.

Download Postman collection Open interactive reference

Contacts

Contacts

People you message. A contact is unique per phone number, so creating one twice is safe — you get the existing record back.

List contacts

GET /api/v1/contacts/

Scope contacts:read

Paginated contacts, newest first.

Query parameters

NameTypeDescription
pageintegerPage number (50 per page).
searchstringMatch on name, phone number or email.
tag_iduuidOnly contacts carrying this tag.
owner_iduuidOnly contacts owned by this teammate.
team_iduuidOnly contacts assigned to this team.
Request
curl -X GET https://api.botpulsar.com/api/v1/contacts/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
      "phone_e164": "+919812345678",
      "display_name": "Ada Lovelace",
      "email": "ada@example.com",
      "source": "api",
      "custom_attributes": {
        "admission_no": "A-1042",
        "class": "8-B"
      },
      "tags": [
        {
          "id": "…",
          "name": "parents",
          "color": "…"
        }
      ],
      "created_at": "2026-10-11T08:30:00Z",
      "updated_at": "2026-10-11T08:30:00Z",
      "...": "..."
    }
  ]
}

Create or find a contact

POST /api/v1/contacts/

Scope contacts:write

Idempotent on the phone number: 201 when a contact is created, 200 with the existing record when the number is already in the workspace. A duplicate is returned untouched — your name, email, tags and fields are not applied to it; use the update call for that.

Request body

FieldTypeRequiredDescription
phone_e164 string Yes E.164 number, max 64 characters.
display_name string No Max 160 characters.
email string No Valid email address.
custom_attributes object No Free-form key/value data you want stored.
tag_names string[] No Up to 50 tags, applied only when the contact is new.
initial_fields object No Values for your custom contact fields, keyed by the field's key. Applied only when the contact is new; an unknown key is a 400.
Request
curl -X POST https://api.botpulsar.com/api/v1/contacts/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_e164": "+919812345678",
    "display_name": "Ada Lovelace",
    "email": "ada@example.com",
    "custom_attributes": {
      "admission_no": "A-1042",
      "class": "8-B"
    },
    "tag_names": [
      "parents"
    ]
  }'
Response
{
  "id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
  "phone_e164": "+919812345678",
  "display_name": "Ada Lovelace",
  "email": "ada@example.com",
  "source": "api",
  "custom_attributes": {
    "admission_no": "A-1042",
    "class": "8-B"
  },
  "tags": [
    {
      "id": "…",
      "name": "parents",
      "color": "…"
    }
  ],
  "created_at": "2026-10-11T08:30:00Z",
  "updated_at": "2026-10-11T08:30:00Z",
  "...": "..."
}
  • A workspace over its contact quota gets 400 with details.detail The workspace contact quota is exhausted.

Get a contact

GET /api/v1/contacts/{id}/

Scope contacts:read

One contact, including tags, consent and active suppressions.

Request
curl -X GET https://api.botpulsar.com/api/v1/contacts/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
  "phone_e164": "+919812345678",
  "display_name": "Ada Lovelace",
  "email": "ada@example.com",
  "source": "api",
  "custom_attributes": {
    "admission_no": "A-1042",
    "class": "8-B"
  },
  "tags": [
    {
      "id": "…",
      "name": "parents",
      "color": "…"
    }
  ],
  "created_at": "2026-10-11T08:30:00Z",
  "updated_at": "2026-10-11T08:30:00Z",
  "...": "..."
}

Update a contact

PATCH /api/v1/contacts/{id}/

Scope contacts:write

Send only the fields you want to change. tag_names and initial_fields are create-only and are refused here.

Request body

FieldTypeRequiredDescription
display_name string No Max 160 characters.
email string No Valid email address.
custom_attributes object No Free-form key/value data.
Request
curl -X PATCH https://api.botpulsar.com/api/v1/contacts/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "Ada King",
    "custom_attributes": {
      "class": "9-A"
    }
  }'
Response
{
  "id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
  "phone_e164": "+919812345678",
  "display_name": "Ada King",
  "email": "ada@example.com",
  "source": "api",
  "custom_attributes": {
    "admission_no": "A-1042",
    "class": "8-B"
  },
  "tags": [
    {
      "id": "…",
      "name": "parents",
      "color": "…"
    }
  ],
  "created_at": "2026-10-11T08:30:00Z",
  "updated_at": "2026-10-11T08:30:00Z",
  "...": "..."
}
Conversations

Conversations

A conversation is one contact on one WhatsApp number. Create it once, then send messages into it. Find your channel_account_id under Dashboard → Channels.

List conversations

GET /api/v1/conversations/

Scope conversations:read

Paginated, most recently active first.

Query parameters

NameTypeDescription
pageintegerPage number.
statusenumopen, pending, snoozed, closed or blocked.
priorityenumlow, normal, high or urgent.
assigned_touuidTeammate (membership) id.
phone_number_iduuidOnly conversations on this WhatsApp number.
searchstringMatch on contact name or number.
Request
curl -X GET https://api.botpulsar.com/api/v1/conversations/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "9a7d3c58-0e1b-4a62-b7d4-6f2c1e8a5b90",
      "status": "open",
      "contact": {
        "id": "…",
        "display_name": "Ada Lovelace"
      },
      "messaging_window": {
        "...": "..."
      },
      "unread_count": 2,
      "last_message_at": "2026-10-11T08:31:02Z",
      "...": "..."
    }
  ]
}

Start a conversation

POST /api/v1/conversations/

Scope conversations:write

Idempotent: the same contact on the same channel returns the existing open conversation instead of opening a second one.

Request body

FieldTypeRequiredDescription
contact_id uuid Yes From the contacts endpoints.
channel_account_id uuid Yes The WhatsApp channel to send from.
phone_number_id uuid No Pick a specific number when the channel has several.
Request
curl -X POST https://api.botpulsar.com/api/v1/conversations/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
    "channel_account_id": "YOUR_CHANNEL_ID"
  }'
Response
{
  "id": "9a7d3c58-0e1b-4a62-b7d4-6f2c1e8a5b90",
  "status": "open",
  "...": "..."
}

Get a conversation

GET /api/v1/conversations/{id}/

Scope conversations:read

Includes messaging_window — whether free-form text is currently allowed or a template is required.

Request
curl -X GET https://api.botpulsar.com/api/v1/conversations/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "9a7d3c58-0e1b-4a62-b7d4-6f2c1e8a5b90",
  "status": "open",
  "priority": "normal",
  "contact": {
    "id": "…",
    "display_name": "Ada Lovelace"
  },
  "channel_account": {
    "id": "…",
    "name": "School WhatsApp"
  },
  "messaging_window": {
    "...": "..."
  },
  "...": "..."
}
Messages

Messages

Sending is asynchronous: the call returns the message as queued straight away and its progress arrives as message.delivery_status.changed webhooks (or poll the list).

List messages in a conversation

GET /api/v1/conversations/{id}/messages/

Scope messages:read

Paginated message history, inbound and outbound.

Query parameters

NameTypeDescription
pageintegerPage number.
Request
curl -X GET https://api.botpulsar.com/api/v1/conversations/YOUR_ID/messages/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "6d1c0b0e-52a4-4b7f-9a55-0a43d6a7c0de",
      "client_message_id": "0b5c1a3e-8f6d-4a3e-9c2a-1d9b7f4e6a10",
      "direction": "outbound",
      "content_type": "text",
      "text": "Hi Ada, your order has shipped!",
      "delivery_status": "queued",
      "provider_message_id": null,
      "created_at": "2026-10-11T08:31:02Z",
      "...": "..."
    }
  ]
}

Send a text message

POST /api/v1/conversations/{id}/messages/

Scope messages:write

Free-form text. Only valid inside the 24-hour window.

Request body

FieldTypeRequiredDescription
content_type enum No text (default).
text string Yes Up to 4,096 characters.
client_message_id uuid No Your idempotency key. Generated for you if omitted.
Request
curl -X POST https://api.botpulsar.com/api/v1/conversations/YOUR_ID/messages/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "content_type": "text",
    "text": "Hi Ada, your order has shipped!",
    "client_message_id": "0b5c1a3e-8f6d-4a3e-9c2a-1d9b7f4e6a10"
  }'
Response
{
  "id": "6d1c0b0e-52a4-4b7f-9a55-0a43d6a7c0de",
  "client_message_id": "0b5c1a3e-8f6d-4a3e-9c2a-1d9b7f4e6a10",
  "direction": "outbound",
  "content_type": "text",
  "text": "Hi Ada, your order has shipped!",
  "delivery_status": "queued",
  "provider_message_id": null,
  "created_at": "2026-10-11T08:31:02Z",
  "...": "..."
}
  • 201 for a new message, 200 when the same client_message_id and body are retried.
  • Reusing a client_message_id with a different body is 409 message_idempotency_conflict.
  • Text outside the 24-hour window is 409 whatsapp_template_required; an opted-out contact is 409 contact_suppressed.

Send a template message

POST /api/v1/conversations/{id}/messages/

Scope messages:write

Required to start a conversation or after the window closes. Get template_id from the templates endpoint; only approved templates can be sent.

Request body

FieldTypeRequiredDescription
content_type enum Yes template.
template_id uuid Yes An approved template.
template_parameters object | array No Variable values: {"1": "Ada"} for numbered variables, or an array in order.
client_message_id uuid No Your idempotency key.
Request
curl -X POST https://api.botpulsar.com/api/v1/conversations/YOUR_ID/messages/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "content_type": "template",
    "template_id": "YOUR_APPROVED_TEMPLATE_ID",
    "template_parameters": {
      "1": "Ada",
      "2": "12045"
    }
  }'
Response
{
  "id": "6d1c0b0e-52a4-4b7f-9a55-0a43d6a7c0de",
  "client_message_id": "0b5c1a3e-8f6d-4a3e-9c2a-1d9b7f4e6a10",
  "direction": "outbound",
  "content_type": "template",
  "text": "Hi Ada, order 12045 has shipped.",
  "delivery_status": "queued",
  "provider_message_id": null,
  "created_at": "2026-10-11T08:31:02Z",
  "...": "..."
}

Send an image, video, audio or document

POST /api/v1/conversations/{id}/messages/

Scope messages:write

content_type is image, video, audio, document, voice_note or sticker. Upload the file in Dashboard → Media library first and pass its id — uploading through the API (media:write) is not open to API keys yet.

Request body

FieldTypeRequiredDescription
content_type enum Yes The media type.
media_asset_id uuid Yes A ready asset from the Media library.
text string No Caption (images, video, documents only).
Request
curl -X POST https://api.botpulsar.com/api/v1/conversations/YOUR_ID/messages/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "content_type": "document",
    "media_asset_id": "YOUR_MEDIA_ASSET_ID",
    "text": "Fee receipt"
  }'
Response
{
  "id": "6d1c0b0e-52a4-4b7f-9a55-0a43d6a7c0de",
  "client_message_id": "0b5c1a3e-8f6d-4a3e-9c2a-1d9b7f4e6a10",
  "direction": "outbound",
  "content_type": "document",
  "text": "Fee receipt",
  "delivery_status": "queued",
  "provider_message_id": null,
  "created_at": "2026-10-11T08:31:02Z",
  "...": "..."
}
Templates

Templates

WhatsApp message templates, read-only over the API. Create and submit them in Dashboard → Templates (Meta reviews them); the API tells you which are approved and what variables they take.

List templates

GET /api/v1/whatsapp/templates/

Scope templates:read

Every template on the workspace, with its review status.

Query parameters

NameTypeDescription
pageintegerPage number.
Request
curl -X GET https://api.botpulsar.com/api/v1/whatsapp/templates/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "e5f0a9d4-2b3c-4d1e-8f7a-0c6b5a4d3e21",
      "name": "order_shipped",
      "language": "en",
      "category": "utility",
      "status": "approved",
      "quality_rating": "green",
      "rejection_reason": "",
      "...": "..."
    }
  ]
}
  • status is one of draft, pending, approved, rejected, paused, disabled, deleted. Only approved can be sent.

Get a template

GET /api/v1/whatsapp/templates/{id}/

Scope templates:read

Full template including its components and versions.

Request
curl -X GET https://api.botpulsar.com/api/v1/whatsapp/templates/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "e5f0a9d4-2b3c-4d1e-8f7a-0c6b5a4d3e21",
  "name": "order_shipped",
  "status": "approved",
  "current_version": {
    "...": "..."
  },
  "...": "..."
}

Template variables

GET /api/v1/whatsapp/templates/{id}/variable-suggestions/

Scope templates:read

The variables a template expects, its example values, saved defaults and suggested contact fields to fill each one from.

Request
curl -X GET https://api.botpulsar.com/api/v1/whatsapp/templates/YOUR_ID/variable-suggestions/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "style": "numbered",
  "variables": [
    "1",
    "2"
  ],
  "examples": {
    "1": "Ada",
    "2": "12045"
  },
  "defaults": {},
  "suggestions": {
    "...": "..."
  }
}
API campaigns

API campaigns

The one-call way to fire a template at a number from another system. Create an API campaign once (it pins an approved template), then send to it by name. The send call also creates or updates the contact, so you do not need the contacts endpoints first. Requires the key's creator to be an owner, admin or manager.

Create an API campaign

POST /api/v1/campaigns/api-campaigns/

Scope campaigns:write

Names must be unique in the workspace.

Request body

FieldTypeRequiredDescription
name string Yes Max 160 characters; used in the send call.
template_id uuid Yes An approved template.
phone_number_id uuid No Send from a specific number.
media_asset_id uuid No Default header media.
is_active boolean No Inactive campaigns refuse sends.
Request
curl -X POST https://api.botpulsar.com/api/v1/campaigns/api-campaigns/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "fee-reminder",
    "template_id": "YOUR_APPROVED_TEMPLATE_ID"
  }'
Response
{
  "id": "c1a2b3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "name": "fee-reminder",
  "template_name": "fee_due",
  "template_status": "approved",
  "variables": [
    "1",
    "2"
  ],
  "is_active": true,
  "metrics": {
    "sent": 0,
    "delivered": 0,
    "read": 0,
    "failed": 0,
    "last_sent_at": null
  },
  "...": "..."
}

Send through an API campaign

POST /api/v1/campaigns/api-campaigns/send/

Scope messages:write

Creates or updates the contact, then sends the campaign's template. Also accepts campaignName, userName and templateParams, so an integration written for AiSensy only needs a new URL and key.

Request body

FieldTypeRequiredDescription
campaign_name string Yes Name of an active API campaign.
destination string Yes Recipient number, max 32 characters.
user_name string No Stored as the contact's name.
template_params array | object No Values in variable order, or {"1": "…"}. Text or numbers only.
media_asset_id uuid No Header media for this send.
tags string[] No Tags to add to the contact.
attributes object No Attributes to merge onto the contact.
client_message_id uuid No Your idempotency key.
Request
curl -X POST https://api.botpulsar.com/api/v1/campaigns/api-campaigns/send/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "campaign_name": "fee-reminder",
    "destination": "+919812345678",
    "user_name": "Ada Lovelace",
    "template_params": [
      "Ada",
      "₹4,500",
      "15 Oct"
    ],
    "tags": [
      "fee-due"
    ]
  }'
Response
{
  "id": "7b1e5d2c-3a4f-4c6d-9e8a-1f0b2c3d4e5f",
  "message_id": "6d1c0b0e-52a4-4b7f-9a55-0a43d6a7c0de",
  "contact_id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c",
  "contact_created": true,
  "status": "queued"
}

List API campaigns

GET /api/v1/campaigns/api-campaigns/

Scope campaigns:read

Each campaign carries live metrics (sent, delivered, read, failed).

Query parameters

NameTypeDescription
pageintegerPage number.
Request
curl -X GET https://api.botpulsar.com/api/v1/campaigns/api-campaigns/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "…",
      "name": "fee-reminder",
      "metrics": {
        "sent": 120,
        "delivered": 117,
        "read": 80,
        "failed": 3
      },
      "...": "..."
    }
  ]
}

Get an API campaign

GET /api/v1/campaigns/api-campaigns/{id}/

Scope campaigns:read

One campaign with its metrics.

Request
curl -X GET https://api.botpulsar.com/api/v1/campaigns/api-campaigns/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "…",
  "name": "fee-reminder",
  "is_active": true,
  "metrics": {
    "sent": 120
  },
  "...": "..."
}

Update or pause an API campaign

PATCH /api/v1/campaigns/api-campaigns/{id}/

Scope campaigns:write

Change the template, number or media, or set is_active to false to pause.

Request body

FieldTypeRequiredDescription
name string No Rename.
template_id uuid No Switch to another approved template.
is_active boolean No false pauses sending.
Request
curl -X PATCH https://api.botpulsar.com/api/v1/campaigns/api-campaigns/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": false
  }'
Response
{
  "id": "…",
  "name": "fee-reminder",
  "is_active": false,
  "...": "..."
}

List a campaign's sends

GET /api/v1/campaigns/api-campaigns/{id}/sends/

Scope campaigns:read

One row per message with its status and, when it failed, a readable reason.

Query parameters

NameTypeDescription
pageintegerPage number.
Request
curl -X GET https://api.botpulsar.com/api/v1/campaigns/api-campaigns/YOUR_ID/sends/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "…",
      "contact_id": "…",
      "name": "Ada Lovelace",
      "phone_e164": "+91981•••5678",
      "status": "delivered",
      "reason": "",
      "created_at": "2026-10-11T08:31:02Z"
    }
  ]
}
Automations

Automations

Start a published automation from your own system — an order was placed, a form was submitted, a payment cleared. The automation must be published with the API request trigger.

Trigger an automation

POST /api/v1/automations/{id}/trigger/

Scope automations:write

Requires an Idempotency-Key header (1–120 letters, numbers, ., _, : or -). The same key returns duplicate: true instead of running twice. The key's creator needs automation-manage permission.

Request body

FieldTypeRequiredDescription
event_name string Yes Max 80 characters; available to the automation as the event.
contact_id uuid No Run it for this contact.
conversation_id uuid No Run it inside this conversation.
Request
curl -X POST https://api.botpulsar.com/api/v1/automations/YOUR_ID/trigger/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Idempotency-Key: order-12045-paid" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "order_paid",
    "contact_id": "3f9c7e2a-1b4d-4e8a-9c3f-2a1b4d4e8a9c"
  }'
Response
{
  "accepted": true,
  "duplicate": false,
  "run_ids": [
    "…"
  ]
}
  • Unpublished or non-API automations return 409.
Webhook subscriptions

Webhook subscriptions

Create and manage webhook endpoints from code instead of the dashboard. How to verify what arrives is on the Webhooks page.

Create a subscription

POST /api/v1/developer/webhook-subscriptions/

Scope webhooks:write

The signing secret is in this response and never shown again — store it immediately. The URL must be public HTTPS.

Request body

FieldTypeRequiredDescription
name string Yes Max 160 characters.
endpoint_url url Yes Public HTTPS URL, max 2,048 characters.
event_types string[] Yes From the event list on the Webhooks page.
Request
curl -X POST https://api.botpulsar.com/api/v1/developer/webhook-subscriptions/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ERP events",
    "endpoint_url": "https://erp.example.com/hooks/botpulsar",
    "event_types": [
      "message.received",
      "message.delivery_status.changed"
    ]
  }'
Response
{
  "subscription": {
    "id": "b2c4f1e0-7a3d-4d0f-8e5b-4a6c9d1e2f33",
    "name": "ERP events",
    "endpoint_url": "https://erp.example.com/hooks/botpulsar",
    "event_types": [
      "message.received",
      "message.delivery_status.changed"
    ],
    "is_active": true,
    "secret_version": 1,
    "created_at": "2026-10-11T08:40:00Z"
  },
  "signing_secret": "whsec_…"
}

List subscriptions

GET /api/v1/developer/webhook-subscriptions/

Scope webhooks:read

All subscriptions, paginated.

Request
curl -X GET https://api.botpulsar.com/api/v1/developer/webhook-subscriptions/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "b2c4f1e0-7a3d-4d0f-8e5b-4a6c9d1e2f33",
      "name": "ERP events",
      "endpoint_url": "https://erp.example.com/hooks/botpulsar",
      "event_types": [
        "message.received",
        "message.delivery_status.changed"
      ],
      "is_active": true,
      "secret_version": 1,
      "created_at": "2026-10-11T08:40:00Z"
    }
  ]
}

Get a subscription

GET /api/v1/developer/webhook-subscriptions/{id}/

Scope webhooks:read

One subscription (the secret is never included).

Request
curl -X GET https://api.botpulsar.com/api/v1/developer/webhook-subscriptions/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "b2c4f1e0-7a3d-4d0f-8e5b-4a6c9d1e2f33",
  "name": "ERP events",
  "endpoint_url": "https://erp.example.com/hooks/botpulsar",
  "event_types": [
    "message.received",
    "message.delivery_status.changed"
  ],
  "is_active": true,
  "secret_version": 1,
  "created_at": "2026-10-11T08:40:00Z"
}

Update or disable a subscription

PATCH /api/v1/developer/webhook-subscriptions/{id}/

Scope webhooks:write

Change the URL or events, or set is_active to false to pause deliveries.

Request body

FieldTypeRequiredDescription
name string No Rename.
endpoint_url url No New public HTTPS URL.
event_types string[] No Replaces the event list.
is_active boolean No false stops deliveries.
Request
curl -X PATCH https://api.botpulsar.com/api/v1/developer/webhook-subscriptions/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": false
  }'
Response
{
  "id": "b2c4f1e0-7a3d-4d0f-8e5b-4a6c9d1e2f33",
  "name": "ERP events",
  "endpoint_url": "https://erp.example.com/hooks/botpulsar",
  "event_types": [
    "message.received",
    "message.delivery_status.changed"
  ],
  "is_active": false,
  "secret_version": 1,
  "created_at": "2026-10-11T08:40:00Z"
}

Rotate the signing secret

POST /api/v1/developer/webhook-subscriptions/{id}/rotate-secret/

Scope webhooks:write

Invalidates the old secret immediately and returns the new one once.

Request
curl -X POST https://api.botpulsar.com/api/v1/developer/webhook-subscriptions/YOUR_ID/rotate-secret/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "subscription": {
    "id": "b2c4f1e0-7a3d-4d0f-8e5b-4a6c9d1e2f33",
    "name": "ERP events",
    "endpoint_url": "https://erp.example.com/hooks/botpulsar",
    "event_types": [
      "message.received",
      "message.delivery_status.changed"
    ],
    "is_active": true,
    "secret_version": 2,
    "created_at": "2026-10-11T08:40:00Z"
  },
  "signing_secret": "whsec_…"
}

List deliveries

GET /api/v1/developer/webhook-deliveries/

Scope webhooks:read

Delivery evidence: status (pending, delivering, failed, delivered, dead), attempts and your endpoint's response code. Records hold hashes, not payload bodies.

Query parameters

NameTypeDescription
subscriptionuuidOnly deliveries for this subscription.
pageintegerPage number.
Request
curl -X GET https://api.botpulsar.com/api/v1/developer/webhook-deliveries/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "…",
      "subscription": "…",
      "event_type": "message.received",
      "status": "delivered",
      "attempt_count": 1,
      "response_status": 200,
      "delivered_at": "2026-10-11T08:31:05Z",
      "...": "..."
    }
  ]
}

Get a delivery

GET /api/v1/developer/webhook-deliveries/{id}/

Scope webhooks:read

One delivery with every attempt.

Request
curl -X GET https://api.botpulsar.com/api/v1/developer/webhook-deliveries/YOUR_ID/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "…",
  "status": "failed",
  "attempt_count": 3,
  "response_status": 500,
  "last_error_code": "http_5xx",
  "attempts": [
    "…"
  ],
  "...": "..."
}

Replay a delivery

POST /api/v1/developer/webhook-deliveries/{id}/replay/

Scope webhooks:write

Re-send a failed or dead-lettered delivery once your endpoint is healthy again.

Request
curl -X POST https://api.botpulsar.com/api/v1/developer/webhook-deliveries/YOUR_ID/replay/ \
  -H "Authorization: ApiKey smk_your_prefix_your_secret" \
  -H "X-Tenant-ID: YOUR_WORKSPACE_ID"
Response
{
  "id": "…",
  "replay_of": "…",
  "status": "pending",
  "...": "..."
}

Start on WhatsApp today — from ₹300/month

Unlimited team members on every plan. 14-day trial, no setup fee, no annual lock-in.