API, webhooks and Zapier
Connect your website and other software to Ellibo CRM. Add and read leads, deals and contacts with the API, get told the moment something happens with webhooks, and use Zapier, Make or Pabbly Connect without writing code.
The API and webhooks are on the Standard plan and up. A CRM admin sets them up in Settings › API & webhooks.
Without writing code: Zapier, Make, Pabbly Connect
When something happens in CRM (e.g. a deal is won), start a task in another app:
- In Zapier, create a Zap with the trigger Webhooks by Zapier › Catch Hook and copy the address it shows. In Make use Webhooks › Custom webhook; in Pabbly Connect use Webhook.
- In CRM go to Settings › API & webhooks › New webhook, paste the address and tick the events, e.g. A deal is won.
- Click Send test. Zapier now has a sample to map fields from.
To add or change CRM records from another app (e.g. a new Google Sheets row becomes a lead):
- In CRM, make an API key (Read & write).
In Zapier use Webhooks by Zapier › Custom Request. In Make use HTTP › Make a request.
- Method POST.
- URL
https://www.ellibo.com/crm/api.php/v1/leads. - Header
Authorization: Bearer <your key>. - Body like the examples below.
API keys
- Read & write or Read only.
- Works as: each key acts as one CRM person. Their role (and custom role) decides what the key can see and change. Records the key adds belong to that person unless you give an owner.
- The key is shown once when you make it. Keep it secret, like a password. If it leaks, switch it off or delete it, and make a new one.
- Every change made with a key goes into the audit log, marked "via API: key name".
Send the key with every request:
Authorization: Bearer ek_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
The address
All requests go to https://www.ellibo.com/crm/api.php/v1/... and use JSON. Try it:
curl https://www.ellibo.com/crm/api.php/v1/me \
-H "Authorization: Bearer <your key>"
| What | Method and path |
|---|---|
| List | GET /v1/leads (also deals, contacts, companies, activities, products, quotes) |
| One record | GET /v1/leads/123 |
| Add | POST /v1/leads |
| Change (only the fields you send) | PATCH /v1/leads/123 |
| Delete | DELETE /v1/leads/123 (leads, deals, contacts, activities) |
| Deal won / lost / reopen | POST /v1/deals/123/won, /lost (lost_reason, lost_note), /reopen |
| Activity done | POST /v1/activities/123/done (outcome) |
| Notes | GET /v1/notes?record_type=deal&record_id=123, POST /v1/notes |
| Lists | GET /v1/pipelines (with stages), /v1/users, /v1/sources, /v1/lost-reasons, /v1/fields (custom fields) |
Quotes, orders and invoices can be read (GET /v1/quotes?type=quote), not changed, because they are documents in your books.
Examples
Add a lead:
POST /v1/leads
{
"first_name": "Nila",
"last_name": "Rao",
"company": "Rao Textiles",
"email": "nila@raotex.in",
"mobile": "98450 11111",
"source": "Website",
"value": 125000,
"tags": ["website"],
"custom_fields": { "Budget": 150000 }
}
The answer is 201 with the whole lead, including its id. Leave out owner_id and the lead is the key person's. Send "owner_id": "auto" to use your assignment rules.
Add a deal, with a new company and contact made for you:
POST /v1/deals
{
"name": "Looms order",
"amount": 480000,
"company_name": "Rao Textiles Pvt Ltd",
"contact_name": "Nila Rao",
"contact_email": "nila@raotex.in",
"close_date": "2026-12-15"
}
Use company_id / contact_id instead for ones already in CRM. Move a deal with PATCH /v1/deals/45 {"stage_id": 12}; the stage ids are in GET /v1/pipelines.
Plan a call: POST /v1/activities {"type": "call", "subject": "Call Nila", "due_at": "2026-11-20 11:30", "deal_id": 45}. Times are in your business's time zone.
Lists, searching and syncing
- Pages:
?page=2&per_page=100(up to 100). The answer hashas_more. Filters:
- leads:
status,owner_id,email,q(name, company, email, phone); - deals:
status,pipeline_id,stage_id,company_id,owner_id; - activities:
deal_id,lead_id,done,due_from,due_to.
- leads:
- Only what changed:
?updated_since=2026-11-01T00:00:00Z&sort=updated. Keep the lastupdated_atyou saw and ask again from there. - All times in answers are UTC (
...Z), except an activity'sdue_at, which is in business time.
Answers and errors
| Code | Meaning |
|---|---|
| 200 / 201 | Done. data holds the record (or the list). |
| 401 | No key, a wrong key, or the key was switched off. |
| 403 | The key's person or plan doesn't allow it (e.g. a read-only key writing). |
| 404 | No such record, or the key's person can't see it. |
| 409 | possible_duplicate: the same email or phone is already in CRM. matches lists them; send "force": true to add it anyway. |
| 422 | validation_error: field says which value to fix. |
| 429 | Too many calls: 120 a minute per key, and the plan's daily total. |
Errors look like {"error": {"code": "validation_error", "message": "Enter the name.", "field": "first_name"}}.
Daily calls: Standard 2,000, Professional 10,000, Business 50,000. The headers X-RateLimit-Limit and X-RateLimit-Remaining show where you are.
Webhooks
A webhook tells another program about events the moment they happen:
| Records | Events |
|---|---|
| Leads | lead.created, lead.updated, lead.converted, lead.deleted |
| Deals | deal.created, deal.updated, deal.stage_changed, deal.won, deal.lost, deal.deleted |
| Contacts and companies | contact.created, contact.updated, contact.deleted, company.created, company.updated |
| Activities | activity.created, activity.completed |
| Quotes | quote.accepted, quote.declined |
Each delivery is a POST with JSON:
{
"id": "evt_3f1c9a...",
"event": "deal.won",
"created_at": "2026-11-02T09:14:05Z",
"business_id": 1234,
"data": { "id": 45, "name": "Looms order", "amount": 480000, "status": "won", ... }
}
- Answer with any 2xx code within 10 seconds. Otherwise CRM tries again after 1, 5 and 15 minutes, then 1, 3, 6 and 12 hours. If the last try fails, CRM gives up on that delivery and your admins get an alert.
- Deliveries shows what was sent and what the address answered. Click Send again on any delivery CRM gave up on.
- Workflows can send too: add the step Send to a web address (event
workflow.run, with the workflow's name and the record). - Addresses must start with
https://and be public.
Check that it came from Ellibo
Every delivery has the headers X-Ellibo-Timestamp and X-Ellibo-Signature: sha256=<hex>. The signature is the HMAC-SHA256 of timestamp + "." + body, with the webhook's signing secret (shown when you edit the webhook). For example, in PHP:
$sig = 'sha256=' . hash_hmac('sha256', $_SERVER['HTTP_X_ELLIBO_TIMESTAMP'] . '.' . file_get_contents('php://input'), $secret);
$ok = hash_equals($sig, $_SERVER['HTTP_X_ELLIBO_SIGNATURE']);
Also refuse deliveries whose timestamp is more than 5 minutes old.