Skip to content
Elara One docs

CRM change webhooks

Get a signed HTTPS request whenever a contact, company, task or lead changes in a store's CRM, with ids and field names only.

CRM webhooks tell your systems about changes in a store's CRM as they happen: a new contact, a stage change, a completed task, a new lead. Each event is posted as JSON to your HTTPS endpoint and signed with that endpoint's own secret. Payloads carry ids, stage keys and the names of the fields that changed, never personal details. To read the values, your receiver calls the merchant CRM API.

Set up an endpoint

In the store admin, go to Settings → CRM → Webhooks and add an endpoint with its URL, the events it wants and a description.

  • The URL must be https:// and must resolve to a public address. Private, loopback and reserved addresses are refused, as is a name that does not resolve.
  • A store can have up to 20 endpoints.
  • Each endpoint has its own signing secret, starting whsec_. It is shown once, when the endpoint is created. Copy it into your receiver's configuration straight away.
  • New secret makes a new one, shown once. The old secret stops working at once, so update your receiver at the same time.
  • Send a test sends one signed webhook.test event now.
  • Switch off pauses an endpoint: changes send nothing to it until you switch it back on.

The signing secret is per endpoint. It is never the store's API secret.

Events

EventWhendata
contact.createdA contact got its CRM record (a form, a message, an order, the admin, the API…).object, id, changed, actor
contact.updatedA contact's CRM fields changed: name, email, phone, title, stage, owner, company, tags or custom fields.object, id, changed, actor
contact.mergedTwo contacts were merged.object, id (the contact that stays), merged_id, merge_id
contact.erasedA contact was erased.object, id, and nothing else
company.createdA company was created.object, id, changed, actor
company.updatedA company changed.object, id, changed, actor
stage.changedA contact's or company's lifecycle stage changed. Sent as well as the *.created or *.updated event.object, id, from, to, actor
task.createdA task was created, including the next task of a recurring one.object (task), id, records, actor
task.completedA task was completed.object (task), id, records, next_task_id, actor
lead.createdA new lead opened.object (lead), id, contact_id, source, owner_id
webhook.testSend a test was pressed.object (endpoint), id, message
  • actor.type says who made the change: staff, system, ai, import, flow, api or merge.
  • changed holds field names: first_name, last_name, title, email, phone, lifecycle_stage, owner, company, tags and custom.{key} for contacts; name, domains, address_*, industry, size and is_sole_trader for companies.
  • from and to are stage keys, such as lead and customer.
  • records lists what a task is about, such as contact:12.

The payload

Every event has the same envelope:

POST to your endpoint
{
  "id": "01JB3K8Q2V7N4M6P9R1T3W5Y7Z",
  "type": "contact.updated",
  "created_at": "2026-10-24T09:30:00+00:00",
  "store": "hearth-and-oak",
  "api_version": "2026-10-01",
  "data": {
    "object": "contact",
    "id": 12,
    "changed": ["email", "lifecycle_stage"],
    "actor": { "type": "api" }
  }
}
FieldMeaning
idThe event id. The same on every retry of this event.
typeThe event, as in the table above.
created_atWhen the event happened, ISO 8601 in UTC.
storeThe store's slug.
api_versionThe payload version, 2026-10-01.
dataThe event's details.

Never sent: names, email addresses, phone numbers, postal addresses, custom field values, tag values, note or task text, or message content. A sensitive custom field's name can appear in changed, but its value never does.

Headers

HeaderValue
Content-Typeapplication/json
User-AgentElaraOne-Webhooks/1.0
X-ElaraOne-EventThe event, such as contact.updated.
X-ElaraOne-DeliveryThe event id (the body's id). The same on every retry: use it to ignore repeats.
X-ElaraOne-Signaturet=<unix time>,v1=<hex HMAC-SHA256>. See Verify signatures.
X-ElaraOne-Environmentstaging, only on events from a staging store.

A staging store signs with its own endpoint secrets. If one receiver serves both, use X-ElaraOne-Environment to keep test events out of live data.

Handle an event

  1. Read the raw body and verify the signature. Refuse anything that fails.
  2. Skip the event if you have already processed its X-ElaraOne-Delivery.
  3. Answer 2xx straight away. Each attempt times out after 10 seconds, and redirects are not followed.
  4. Do the work afterwards, on a queue: fetch the record through the merchant CRM API and update your system.
Fetch the contact after contact.updated
const res = await fetch(`https://api.elaraone.app/api/v1/merchant/crm/contacts/${event.data.id}`, {
  headers: {
    Accept: 'application/json',
    'X-Tenant-Key': process.env.ELARAONE_TENANT_KEY!,
    'X-Tenant-Secret': process.env.ELARAONE_TENANT_SECRET!,
    Authorization: `Bearer ${process.env.ELARAONE_MERCHANT_KEY}`, // a key with crm:read
  },
})

The API checks the key's access, so your copy is only as complete as the key allows. A contact that has been merged into another answers 404; follow contact.merged to the contact that stays. On contact.erased, delete everything you hold about that contact id.

Events for one record usually arrive in order, but a retry can overtake a later event. Use created_at, or simply read the record's current state through the API, rather than trusting arrival order.