# Webhook payload reference

URL: https://useembers.com/help/integrations/webhook-payload-reference/
Category: Integrations
Plan: Solo and above
Updated: 2026-09-12
Last verified: 2026-09-12

> The Embers event envelope, the versions on it, every event type, the lead object, the contact rules, and the key you should join on.

Every Embers webhook is a JSON `POST` with the same envelope. This page describes what is in it and which fields you should build on.

> **Warning: Solo and above, administrators only**
>
> Webhooks are configured under Developer, then Webhooks (`/developer/webhooks`) on Solo or above, by an account administrator.

## The envelope

Every request carries a stable event `id`, the event `type`, two version strings, a `created_at`, the `account` that produced it, and a `data` object shaped by the event.

- `api_version` is `2026-07-20`. It only changes if the envelope is reshaped in a way a receiver cannot absorb by ignoring unknown keys, which has not happened.
- `schema_version` is `2026-09-10`. It records field-level changes, so a key that is added or dropped bumps this and leaves `api_version` alone. 2026-09-10 dropped `account.type`; route on `account.id` and `account.name`.
- `account` carries the account id and name, so agencies can route events for several client accounts without looking up a UUID.

Ignore keys you do not recognise.

## Event types

| Type | When it fires |
| --- | --- |
| `lead.created` | Embers creates a lead score for a person in your account. Carries the engagement that triggered it |
| `lead.engagement.added` | A qualified lead you already have engages again. One event per lead per scan, with every new engagement batched in. Not sent alongside `lead.created` |
| `lead.status_changed` | Someone moves a lead to a different pipeline status. Carries `previous_status`, `status`, and `changed_at` |
| `report.generated` | A client report finishes generating or regenerating. Carries the report id, title, status, date range, reason, and public share URL, but not the report snapshot |
| `webhook.test` | An administrator sent a sample from Developer, then Webhooks. `data.is_test` is always `true` and `data.sample_event_type` names the shape that was chosen |

On `lead.engagement.added`, `data.engagement.engagement_count` is the true total and `data.engagement.engagements` is capped at 50 items, with `truncated` set when the array is shorter than the count.

## The lead object

`data.lead` carries `id`, `name`, `title`, `company`, `linkedin_url`, `linkedin_urn`, `score`, `icp_match`, `icp_confidence`, `icp_reason`, `trend`, `top_signals`, `sources`, `status`, `follower_count`, and `first_seen_at`, plus `work_history` and `certifications` capped at 10 entries each.

`score` and `icp_confidence` use the same 0 to 100 scale shown in the app.

There is no first name or last name field. The payload has one `name`.

Two numbers the app shows are derived rather than sent. Engagement rate is `engagement_count` divided by `follower_count`, which is why `follower_count` is included. Grade comes from `score`: A at 80 or more, B at 60, C at 40, D at 20, F below that.

## Join on linkedin_urn

`lead.linkedin_urn` is LinkedIn's own immutable member id, and it is the key to join on. Do not key on `linkedin_url`: members change their vanity slug, and some people are only ever seen in URN form, so the same person can appear under two different URLs. `lead.id` is the Embers UUID and is stable per account and member, but if two member records are merged the losing UUID stops appearing. On older records `linkedin_urn` may be blank, so fall back to `lead.id`.

## Contact fields

`lead.contact` is additive and gated by plan.

- **Free**: the key is omitted entirely.
- **Solo and above**: the key is present, carrying `email`, `phone`, `enriched_at`, `last_verified_at`, `included`, `source`, `confidence`, and `status`.

Only values Embers already stored are copied. Missing values are `null`, and `included` is `true` only when a stored value exists. Webhooks never trigger a fresh enrichment and never invent an address or a number. Comment text can still contain contact details the person published themselves, but that is public post content, not the structured `contact` object.

## Engagement timestamps

`engaged_at_precision` tells you whether `engaged_at` is a measurement or a substitute. LinkedIn publishes a timestamp for comments and replies, so those are `exact`. Reactions are `approximate`, where Embers substitutes the time it first saw the reaction or the parent post's date. Order a follow-up sequence on `exact` rows only.

## Example

```json
{
  "id": "e1b4f6e8-4c4a-4f50-b3c7-2ef2f97d5012",
  "type": "lead.status_changed",
  "api_version": "2026-07-20",
  "schema_version": "2026-09-10",
  "created_at": "2026-07-20T10:00:00+00:00",
  "account": {
    "id": "4d2f0eb9-81e5-4f20-8a60-25cbb9fe9ea5",
    "name": "Acme LinkedIn"
  },
  "data": {
    "lead": {
      "id": "967caf1c-c0a6-4b98-9f3b-1e0811fb53b4",
      "name": "Alex Smith",
      "title": "VP Sales",
      "company": "Acme",
      "linkedin_url": "https://www.linkedin.com/in/alex-smith/",
      "linkedin_urn": "ACoAAABBBCCCDDDEEEFFFGGG",
      "score": 82,
      "icp_match": true,
      "icp_confidence": 90,
      "icp_reason": "Strong insurance principal with clear sales authority.",
      "trend": "rising",
      "top_signals": ["Visited pricing"],
      "sources": ["post_engagement"],
      "status": "contacted",
      "follower_count": 4200,
      "first_seen_at": "2026-06-02T09:15:00+00:00",
      "contact": {
        "email": "alex@acme.example",
        "phone": "+13125550100",
        "enriched_at": "2026-07-01T00:00:00+00:00",
        "last_verified_at": "2026-07-01T00:00:00+00:00",
        "included": true,
        "source": "harvest",
        "confidence": "safe",
        "status": "ready"
      }
    },
    "engagement": {
      "recent": [
        {
          "engagement_type": "comment",
          "engagement_category": "comment",
          "engaged_at": "2026-07-19T14:02:00+00:00",
          "engaged_at_precision": "exact",
          "source": "post",
          "comment_text": "This matches what we're seeing at renewal.",
          "post_url": "https://www.linkedin.com/feed/update/7481311828074078208/",
          "post_author_name": "You",
          "post_author_type": "you",
          "post_snippet": "A post about renewal cycles in commercial insurance.",
          "intent_category": "pain_alignment"
        }
      ],
      "by_type": { "comment": 1 },
      "first_engaged_at": "2026-07-19T14:02:00+00:00",
      "last_engaged_at": "2026-07-19T14:02:00+00:00"
    },
    "previous_status": "active",
    "status": "contacted",
    "changed_at": "2026-07-20T10:00:00+00:00"
  }
}
```

## Related

- [Signed webhooks](/help/integrations/webhooks/): Create an endpoint and choose its events.

- [Verify webhook signatures](/help/integrations/verify-webhook-signatures/): Check the HMAC before you trust a request.

- [Retries and pauses](/help/integrations/webhook-retries-and-pauses/): What happens when your endpoint is down.

## Frequently asked questions

**Why is there no first name or last name in the payload?**

The payload carries a single name field. Split names appear in the CSV export, in HubSpot, and on the Slack card, not on the webhook.

**Will a new field break my receiver?**

Most changes are additive. 2026-09-10 removed `account.type`; ignore keys you do not recognise.
