Webhooks

Receive a JSON POST each time something happens in your Emelia campaigns: email sent, opened, clicked, replied, bounced, LinkedIn invitation accepted, WhatsApp reply, campaign finished.

A webhook is a URL that Emelia calls with a JSON POST each time a selected event happens in a campaign: a reply, an acceptance, a bounce. Use it to update your CRM, alert your team or route replies the moment they arrive.

Register a webhook

Webhooks are registered on a campaign, or on all your campaigns at once. Today they are created from the Make integration of Emelia (the "Watch Campaign Activities" and "Watch Campaign Ending" modules), and listed, tested and deleted in the Emelia app, in Settings, API & Webhooks.

Creating webhooks by API

The registration routes are not part of this reference yet. If you need to register webhooks from your own code, contact contact@emelia.io.

List your webhooks

GET/webhook
curl https://api.emelia.io/webhook \
  -H "Authorization: $EMELIA_API_KEY"
Response
{
  "webhooks": [
    {
      "id": "6702c1d4e5f6a7b8c9d0e1f2",
      "typeOfCampaign": "advanced",
      "campaignId": "6651f0c2a1b2c3d4e5f60718",
      "campaignName": "Q4 SaaS founders",
      "url": "https://hooks.yourcompany.com/emelia",
      "events": ["MAIL_REPLIED", "LINKEDIN_REPLIED"],
      "status": "active"
    }
  ]
}

There is one entry per campaign: a URL registered on all your campaigns appears once for each of them. typeOfCampaign is advanced for the campaigns of the current app, email or linkedin for legacy campaigns.

Events of a campaign

These are the event names of the campaigns of the current app. The payload of each is on its page: Campaign activity and Campaign finished.

EventWhen
EMAIL_SENTAn email step was sent. The name is EMAIL_SENT, not MAIL_SENT.
OPENED, CLICKEDThe tracking pixel loaded, or a tracked link was clicked. Needs tracking on the campaign. Delivered 30 to 60 seconds later.
MAIL_REPLIEDFirst email reply of the contact, with reply, sentiment, sender and messageId.
BOUNCEDA bounce was detected. No campaign field, step is 0.
UNSUBSCRIBEDThe contact unsubscribed.
VISITED, INVITED, ACCEPTED, MESSAGE_SENT, INMAIL_SENT, LIKEDLinkedIn actions. MESSAGE_SENT carries the text in content.
LINKEDIN_REPLIEDLinkedIn reply, with reply and sentiment.
WHATSAPP_MESSAGE_SENT, WHATSAPP_REPLIEDWhatsApp message sent, reply received.
CAMPAIGN_ENDINGThe campaign was closed by the daily job, see Campaign finished.

Never sent for these campaigns: SENT, MAIL_SENT, FOLLOWED, TASK_COMPLETED, TASK_FAILED. The events of legacy campaigns have their own pages: Legacy email activity, Legacy LinkedIn activity.

A MAIL_REPLIED delivery looks like this:

{
  "event": "MAIL_REPLIED",
  "campaign": "Q4 SaaS founders",
  "campaignId": "6651f0c2a1b2c3d4e5f60718",
  "senderName": "Marie Martin",
  "sender": "marie.martin@yourcompany.com",
  "contact": {
    "id": "6703d2e5f6a7b8c9d0e1f2a3",
    "firstName": "Claire",
    "lastName": "Fontaine",
    "email": "claire.fontaine@nexora.io",
    "company": "Nexora"
  },
  "date": "2026-10-13T10:20:00.000Z",
  "step": 0,
  "reply": "Hi Marie, sounds interesting. Can we talk on Tuesday?",
  "sentiment": { "classification": "INTERESTED", "score": 0.92 },
  "messageId": "<CAF7x2y3z@mail.nexora.io>"
}
  • contact starts with the custom variables of the contact, then the standard fields. company is the first custom variable whose name contains "company".
  • sentiment is the AI classification of the reply: INTERESTED, NOT_INTERESTED, NEUTRAL, OUT_OF_OFFICE, HOSTILE, DELEGATION, INCOMPREHENSION or QUESTION_ASK, with a score. It is present only when reply classification runs on your account.
  • date is an ISO 8601 date in UTC, usually the time Emelia processed the event.
  • Absent fields are omitted, never null.

Receive the events

Answer fast with a 2xx, then do the work in the background.

# Simulate a delivery to test your endpoint
curl -X POST https://hooks.yourcompany.com/emelia \
  -H "Content-Type: application/json" \
  -d '{"event": "MAIL_REPLIED", "campaign": "Q4 SaaS founders", "contact": {"email": "claire.fontaine@nexora.io"}, "date": "2026-10-13T10:20:00.000Z", "step": 0, "reply": "Can we talk on Tuesday?"}'

Delivery rules

Build your handler for these rules:

  • No signature. Deliveries carry Content-Type: application/json and nothing else: no secret, no signature header. Use a URL that is hard to guess, and treat the payload as a hint: confirm important facts with the API before acting on them.
  • No retry on HTTP errors. Your response is ignored. A few events are retried on network errors only. An endpoint that is down misses the events.
  • No ordering. Events come from different services: an OPENED can arrive after the MAIL_REPLIED.
  • No event id. To deduplicate, build a key from event, campaign, the contact and step. A URL registered twice on a campaign receives everything twice.
  • No timeout on Emelia's side, but answer in a few seconds anyway.

The LinkedIn scraper has its own, separate webhooks (FINISHED and ERROR).

On this page