Skip to Content
DevelopersKeeping your CRM in sync

Keeping your CRM in sync

A company that keeps HubSpot, Zoho or another CRM next to Majali syncs it through server keys and webhooks. Everything here needs a server key with the operation’s scope; a publishable key is refused, and these routes answer no browser.

The operations

OperationScopeWhat it does
GET /api/v1/contactsread:contactsPeople: name, email, phone, language, roles, their leads’ references, your ids
POST /api/v1/contactswrite:contactsAdd or match a person, by your id, then email, then phone; fills, never overwrites
GET /api/v1/contacts/{reference}read:contactsOne person
PATCH /api/v1/contacts/{reference}write:contactsChange the name or language, or keep your id
GET /api/v1/leadsread:leadsLeads, with person, assignee, department, next action and first-answer times
GET /api/v1/leads/{reference}read:leadsOne lead
PATCH /api/v1/leads/{reference}write:leadsStatus, assignee, your id
GET /api/v1/activitiesread:followupsTimeline entries
POST /api/v1/activitieswrite:followupsLog a note, call, meeting, message or email
GET /api/v1/tasksread:followupsTasks
POST /api/v1/taskswrite:followupsAdd a task
GET /api/v1/tasks/{task_id}read:followupsOne task
PATCH /api/v1/tasks/{task_id}write:followupsDone, open again, title, due date, assignee
GET /api/v1/membersread:membersTeam members, to map your users: use their id as assignee or by
GET /api/v1/departmentsread:membersDepartments, by key

The loop

  1. Subscribe a webhook endpoint (the company’s Webhooks screen) to the events you mirror: lead.created, lead.assigned, lead.status_changed, lead.escalated, contact.created, contact.updated, activity.created, task.created, task.done. Events carry ids and references only.
  2. On an event, read the record (GET /api/v1/leads/{reference}, GET /api/v1/contacts/{reference}, or the lists with updated_since). A nightly pull with updated_since catches anything a missed event left behind.
  3. Upsert it in your CRM, then write your id back: PATCH the lead or the person with {"external": {"source": "hubspot", "id": "88213"}}. From then on ?external=hubspot:88213 finds it.
  4. Changes made in your CRM come back through PATCH (status, assignee) and POST /api/v1/activities (a call logged in the CRM lands on the lead’s timeline and counts as its first answer).

No echoes

Every event carries caused_by: admin (someone on a screen), system (scheduled work), website (a publishable key) or api:<key prefix>. Ignore events whose caused_by is your own key’s prefix: they are the echo of your own writes.

Sending twice changes nothing

POST /api/v1/contacts with the same external id, and POST /api/v1/activities or POST /api/v1/tasks with the same source and external_id, return the first record (200 instead of 201). Retry freely after a timeout.

Erasure

A person erased under a privacy request or retention comes back with "erased": true and empty personal fields (a lead the same way). Erase your copy too: the company answers for both.

HubSpot

A private app with the contacts and deals scopes; map Majali people to contacts (source hubspot, the contact’s id) and leads to deals or tickets, with the status as the pipeline stage. Log HubSpot calls and meetings with POST /api/v1/activities, source hubspot and the engagement’s id as external_id.

Zoho CRM

A server-based client; map people to Contacts or Leads and Majali leads to Deals, source zoho. Zoho’s workflow webhooks call your own small service, which writes to Majali with the server key; never give Zoho the key itself.

What the company sees

Every read is recorded in the company’s audit log with the key’s prefix; revoking the key stops the sync at once.