Connect InnoReach to whatever you already use
A REST API for reading and updating leads, plus webhooks so your CRM hears about changes the moment they happen — no polling.
Base URL and authentication
Every endpoint lives under https://innoreach.innobrains.pk/api/v1/
and is authenticated with an API key you create in
Developer settings.
curl https://innoreach.innobrains.pk/api/v1/leads \
-H "Authorization: Bearer inr_your_key_here"
An X-API-Key header works too, if that suits your HTTP
client better. Keys are stored as a hash, so the value is shown once at
creation and cannot be recovered afterwards — if you lose it, revoke the
key and make a new one.
Scopes
A key carries either read, write, or both.
Give a key only what it needs: a reporting integration that can also
rewrite your pipeline is a liability, not a convenience. A request
outside a key's scope returns 403 with
insufficient_scope.
Rate limits
120 read requests and 60 write requests per minute. Exceeding either
returns 429; wait for the window to reset rather than
retrying immediately.
Endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /api/v1/leads |
read | List leads, filtered and paginated |
| GET | /api/v1/leads/{id} |
read | One lead, with its activity history |
| PATCH | /api/v1/leads/{id} |
write | Update status, notes or follow-up date |
| POST | /api/v1/leads/{id}/activities |
write | Record a call, message or meeting |
| GET | /api/v1/account |
read | Licence, credit balance and lead counts |
| GET | /api/v1/meta |
read | Valid statuses, activity types and webhook events |
Keeping a CRM in sync
The field that matters for syncing is updated_since. Store
the highest updated_at you have seen, and pass it back on
the next call to get only what has changed.
GET /api/v1/leads?updated_since=2026-08-26T09:00:00Z&per_page=200
The boundary second is inclusive. Timestamps are stored to the second, so an exclusive comparison would silently drop anything written during the same second as your last poll — which is exactly the kind of bug that loses a customer's lead and is never noticed.
Filters
status— new, contacted, interested, customer, not_interestedcityandcategory— exact matchhas_email/has_phone— only leads that have oneper_page— up to 200, default 50
Response shape
{
"data": [
{
"id": 1042,
"name": "Acme Traders",
"phone": "03027138708",
"phone_international": "+923027138708",
"whatsapp_url": "https://wa.me/923027138708",
"email": "[email protected]",
"website": "https://acme.pk",
"address": "Jail Road, Lahore",
"category": "Wholesaler",
"city": "Lahore",
"country": "Pakistan",
"rating": 4.3,
"status": "interested",
"notes": "Wants a quote by Friday",
"follow_up_at": "2026-08-29T10:00:00+00:00",
"updated_at": "2026-08-26T09:14:02+00:00"
}
],
"meta": { "page": 1, "per_page": 50, "total": 137, "has_more": true }
}
phone_international is the field to dial or message with —
it is normalised from the local format, so you do not need to know each
country's dialling rules.
Writing back
curl -X PATCH https://innoreach.innobrains.pk/api/v1/leads/1042 \
-H "Authorization: Bearer inr_your_key_here" \
-H "Content-Type: application/json" \
-d '{"status":"customer","notes":"Signed a 6-month contract"}'
curl -X POST https://innoreach.innobrains.pk/api/v1/leads/1042/activities \
-H "Authorization: Bearer inr_your_key_here" \
-H "Content-Type: application/json" \
-d '{"type":"call","note":"Discussed pricing","outcome":"interested"}'
Webhooks
Rather than polling, register an HTTPS endpoint and we will POST to it when something changes. Events fire wherever the change came from — the Chrome extension, the Android app, the dashboard, or this API.
lead.created— a new business was collectedlead.updated— status, notes, follow-up or contact details changedactivity.logged— a call or message was recorded
Verifying a delivery
Every request carries X-InnoReach-Signature, an HMAC-SHA256
of the exact request body using your endpoint's signing secret. Verify it
before trusting the payload — otherwise anyone who learns your URL can
post fake leads into your CRM.
// Node.js
const crypto = require('crypto');
function verify(rawBody, headerValue, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
// Constant-time compare, so a timing attack cannot guess the signature.
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(headerValue),
);
}
// PHP
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (! hash_equals($expected, $_SERVER['HTTP_X_INNOREACH_SIGNATURE'])) {
http_response_code(401);
exit;
}
Verify against the raw body, not a re-encoded version of the parsed JSON — re-encoding changes key order and whitespace, and the signature will never match.
Delivery and retries
Return any 2xx status to acknowledge. We retry three times with a short backoff, and an endpoint that fails ten times in a row is paused automatically so a dead URL does not queue forever. You can re-enable it from Developer settings once it is fixed, which also clears the failure count.
Payload
{
"event": "lead.created",
"sent_at": "2026-08-26T09:14:02+00:00",
"data": {
"id": 1042,
"name": "Acme Traders",
"phone_international": "+923027138708",
"email": "[email protected]",
"city": "Lahore",
"status": "new"
}
}
Error responses
| Status | Error | What to do |
|---|---|---|
401 |
missing_api_key |
Send the key in an Authorization or X-API-Key header |
401 |
invalid_api_key |
The key is unknown, revoked or expired — create a new one |
403 |
insufficient_scope |
The key lacks the scope this endpoint needs |
403 |
account_suspended |
Contact support |
404 |
— |
The lead does not exist, or belongs to another account |
422 |
validation |
A field is missing or invalid; the response lists which |
429 |
— |
Rate limited — wait for the window to reset |
A lead belonging to another account returns 404, not
403 — confirming that an id exists would leak information about
other customers.
Build the integration you need
Create a key, point it at your CRM, and pull your leads in. If something is missing from the API, tell us — it is a young surface and we would rather add what people actually use.
- REST API
- Signed webhooks
- Scoped keys