# Webhooks

> Webhooks move data in both directions: your forms and CRMs push leads into MySA to be called, and MySA posts each positive lead out to your own automation.

Source: https://business.mysmartassistant.ai/docs/webhooks

## Two kinds of webhook

| Direction | What it does | Where you set it up |
|---|---|---|
| Inbound | Your system POSTs a lead or message to a MySA URL, and a webhook campaign calls the customer | **Integrations**, **Webhooks** tab |
| Outbound | MySA POSTs every positive lead to your URL (Pabbly, Zapier, Make, n8n or your server) | **Lead Feed**, Webhook card |

Every finished call, with an API for its recording, can also be sent to your server: see [Call webhook & recordings API](https://business.mysmartassistant.ai/docs/call-recordings-api).

CRM partners also receive signed event webhooks such as `call.completed`. Those are covered in [Partner API](https://business.mysmartassistant.ai/docs/partner-api).

## Inbound webhooks

### Create one

1. Open **Integrations** from the left menu and choose the **Webhooks** tab.
2. Click **New webhook**, enter a **Name** and click **Create**.
3. Copy the URL from **Copy your webhook URL**. The secret is shown here once, so copy it now.

Each webhook has a Test URL and a Live URL. The list shows the one for the workspace you are in; switch Test/Live in the top bar to see the other. Both use the same secret.

> **Warning:** The secret is shown only once. If you lose it, use **Rotate secret** on the webhook. The old secret stops working, so update your sender at the same time.

### Send events

POST JSON to the URL with the secret in the `x-webhook-secret` header:

```bash
curl -X POST https://business.mysmartassistant.ai/v1/hooks/YOUR_WEBHOOK_TOKEN \
  -H "Content-Type: application/json" \
  -H "x-webhook-secret: YOUR_WEBHOOK_SECRET" \
  -d '{
    "lead": {
      "phone": "+91 98765 43210",
      "name": "Ravi Kumar",
      "note": "Wants a test ride this weekend"
    }
  }'
```

If your provider only lets you paste a URL and cannot add headers, append the secret as a query parameter instead: `?s=YOUR_WEBHOOK_SECRET`. If both are sent, the header wins.

The response is always `{ "data": { "status": "..." } }`:

| `status` | Meaning |
|---|---|
| `sample_captured` | First event received. It was saved as a sample so you can map it |
| `awaiting_mapping` | A sample already exists; save the mapping to activate the webhook |
| `accepted` | The event was mapped and queued for a call |
| `skipped_no_mobile` | No usable mobile number was found at the mapped path |
| `deactivated` | This account has been deactivated |

A wrong token or secret returns `403 INVALID_WEBHOOK`. The response does not say which of the two was wrong.

### Read the status light

Every webhook on **Integrations → Webhooks** has a coloured light next to its name. Hover it to see the status in words.

| Light | Status | What it means | What to do |
| --- | --- | --- | --- |
| 🔴 Red | **Awaiting first event** | The webhook exists, but no data has reached it yet | Send one real event from your form or CRM |
| 🟢 Green | **Awaiting mapping** | Data has arrived, so its fields can now be mapped | Open **Mapping** and press **Save & activate** |
| 🟢 Green | **Active** | Mapped and ready | Attach it to a webhook campaign |

_Integrations → Webhooks: each webhook with its status light, Test/Live URL and Mapping_

**On this screen:** each webhook shows its status light, its name, the URL to POST to (copy it with the button on the right), and **Mapping**, the key icon (secret) and delete. The line under the URL reminds you how to send the secret.

> **Note:** Only an **Active** webhook appears in a campaign's **Source webhook** list. A red one has no data yet, and a green one still waiting for mapping cannot be attached until you save its mapping.

### Map the fields

Your payload can have any shape. You tell MySA where each value lives.

1. Send one real event. The webhook moves from **Awaiting first event** to **Awaiting mapping**.
2. Open the webhook. **Detected fields (first event)** lists every path in the sample with an example value.
3. Under **Map to call fields**, pick a path for each field:

| Call field | Required |
|---|---|
| **Mobile number** | Yes |
| **Message text** | Yes |
| **Customer name** | No |
| **Customer ID** | No |
| **Language** | No |

4. Click **Save & activate**. The webhook becomes **Active**. Later edits use **Update mapping**.

Paths are dot-paths into your JSON, such as `lead.phone` or `data.metaEvent.contacts[0].profile.name`. Non-digits are stripped from the mobile number.

### The summary window

Customers often send several messages in a row. Messages from the same mobile number that arrive within the **Summary window** are merged into one call. The window starts at the customer's first message, defaults to 30 seconds and can be set from 1 to 30 seconds. The merged messages become the call's top-priority question.

Click the info button on a webhook in the list to see how many events it received today and its recent events.

### Call the leads with a webhook campaign

An **Active** (mapped) webhook does nothing until a campaign uses it as a source — and only Active webhooks can be picked.

1. Go to **Campaigns** and create a campaign.
2. Choose the **Webhook** type and pick an active webhook as the source.
3. Set the assistant and purpose as for any campaign.
4. Set the per-customer limits:
   - Maximum calls to the same number per day: 1 to 10, default 2.
   - Minimum gap between two calls to the same number: at least 60 minutes, up to 1440.
5. Choose whether to also dial events that arrived before the webhook was attached, or only new ones.
6. Publish and run the campaign.

Each lead is dialled as soon as its summary window closes. There is no interval between calls.

> **Note:** In the Test workspace, only numbers on your Test Numbers list are actually dialled. See [Test calls](https://business.mysmartassistant.ai/docs/test-calls) and [Campaigns](https://business.mysmartassistant.ai/docs/campaigns).

## Outbound webhook: positive leads

The Webhook card on the **Lead Feed** page posts every positive lead to a URL you supply, as JSON, within about half a minute of the call being judged. A positive lead is a call that achieved what it was dialled for, the same calls your **Positive Leads** page lists. Lead Feed itself is described in [Lead Feed](https://business.mysmartassistant.ai/docs/lead-feed).

### Set it up

1. In your automation tool, create a catch-webhook step and copy its URL.
2. Paste the URL into the Webhook card on **Lead Feed** and save.
3. Start your tool's capture step, then press **Send test** on the card.

Pabbly, Zapier and Make learn which fields exist from one captured request. The test lead has every field filled in and carries `"sample": true`, so you can filter it out later.

The URL must start with `https://`, be at most 500 characters, contain no username or password, and point at a publicly reachable address. Clear the box to disconnect.

### Request

MySA sends `POST` with `content-type: application/json` and `user-agent: MySA-LeadFeed/1.0`. This is the test lead exactly as it is sent:

```json
{
  "event": "positive_lead",
  "sample": true,
  "callAttemptId": "sample-call-attempt",
  "at": "2026-10-01T15:00:00.000+05:30",
  "customerName": "Sample Customer",
  "phone": "+910000000000",
  "purpose": "Confirm the pending order and agree a delivery date",
  "purposeName": "Order confirmation",
  "purposeScorePct": 100,
  "purposeFields": { "delivery_date": "Friday", "order_id": "SAMPLE-0001" },
  "source": "campaign",
  "campaignName": "Sample Campaign",
  "summary": "This is a sample lead sent from your Lead Feed settings. No customer was called.",
  "nextAction": "Nothing — this is a test.",
  "durationSeconds": 96,
  "tags": ["outcome: sample"],
  "vars": { "var1": "SAMPLE-0001" }
}
```

| Field | Notes |
|---|---|
| `event` | Always `positive_lead` |
| `sample` | `true` only for **Send test** |
| `at` | ISO 8601 in IST, with `+05:30` |
| `purposeScorePct` | The only field that can be `null`: it means nothing was measured, which is different from `0` |
| `tags` | Flattened to `"category: name"` strings |
| `vars` | The variables from your uploaded contacts file |

Every field is present on every request, even when empty, so a mapping you have built never breaks.

### Retries

Reply with any 2xx status to acknowledge. MySA waits up to 10 seconds for an answer.

| Result | What happens |
|---|---|
| 2xx | Delivered |
| No answer, timeout, 5xx, 408 or 429 | Retried after 1 minute, 5 minutes, 30 minutes and 2 hours |
| Any other 4xx (400, 401, 403, 404, 422…) | Not retried, because time will not fix it |

Deliveries that were given up on are counted on the card with a **Try again** button. Fix the URL, press it, and they are sent to wherever the card points now. A lead that never reached your automation is still on your **Positive Leads** page.

> **Note:** Lead Feed runs only in your Live workspace. Test calls and your own **Try now** calls never trigger it, so use **Send test** while you build your workflow.

## Related

- [API keys](https://business.mysmartassistant.ai/docs/api-keys) to push contacts into an API campaign instead
- [Lead Feed](https://business.mysmartassistant.ai/docs/lead-feed) for email, Google Sheet, WhatsApp and the read-only feed URL
- [Partner API](https://business.mysmartassistant.ai/docs/partner-api) for partner event webhooks
