# Webhook events

> The 11 webhook event types, with their payloads, generated from the OpenAPI document.

Source: https://emailmovers.com/mydatahub/docs/reference/webhooks

11 event types. Each delivery is a Standard Webhooks request with three headers, `webhook-id`, `webhook-timestamp`, `webhook-signature`, and a JSON body of the shape below. Delivered to your endpoint under Standard Webhooks: verify webhook-signature (HMAC-SHA256 over webhook-id, webhook-timestamp and the body with your whsec_ secret) and treat webhook-id as the idempotency key. Any 2xx acknowledges; anything else is retried over 24 hours with backoff.

Every payload carries `id` (also the `webhook-id` header, your idempotency key), `type`, `timestamp`, `api_version` and `data`. Ignore members you do not know: they may appear in a later `MyDataHub-Version`.

`GET /v1/events` replays the last 30 days, oldest first, in the same shape. See [Webhooks](https://emailmovers.com/mydatahub/docs/webhooks.md) for signing, retries and verification code.

The example payloads are illustrative. Each is checked against its schema when this page is generated.

## Event types

| Event | Fires when |
| --- | --- |
| [`order.ready`](#order-ready) | Records are available. |
| [`order.failed`](#order-failed) | Processing failed; credits are refunded. |
| [`audience.refreshed`](#audience-refreshed) | A saved audience was recounted, with the diff. |
| [`credits.low`](#credits-low) | The balance crossed the configured threshold. |
| [`credits.topped_up`](#credits-topped-up) | A top-up settled. |
| [`warranty.replacement_issued`](#warranty-replacement-issued) | A bounced or job-changed record was replaced, or re-credited. |
| [`suppression.created`](#suppression-created) | An objection reached a record your organisation holds. Suppress it before any send. |
| [`push.completed`](#push-completed) | A CRM push finished. |
| [`push.partial`](#push-partial) | A CRM push partly finished; retry the failed records. |
| [`push.failed`](#push-failed) | A CRM push failed. |
| [`connection.expired`](#connection-expired) | A CRM token failed; reconnect in the app. |

## order.ready

Records are available.

Payload: `OrderReadyEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `order.ready` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.records` | integer (0 or more) | yes |  |
| `data.records_url` | string (uri-reference) | yes | A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL. |
| `data.receipt_url` | string (uri-reference) | yes | A path on https://mydatahub.emailmovers.com starting /v1/. Resolve it against the origin (RFC 3986), not by appending it to the server URL. |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "order.ready",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "records": 5000,
    "records_url": "/v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/records",
    "receipt_url": "/v1/orders/ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E/receipt"
  }
}
```

## order.failed

Processing failed; credits are refunded.

Payload: `OrderFailedEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `order.failed` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.reason` | string | yes |  |
| `data.credits_refunded` | number (0 or more) | yes | Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed). |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "order.failed",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "reason": "The audience could not be run.",
    "credits_refunded": 300
  }
}
```

## audience.refreshed

A saved audience was recounted, with the diff.

Payload: `AudienceRefreshedEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `audience.refreshed` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | `AudienceRefresh` | yes | A saved audience recounted on the current dataset version, with the difference. |
| `data.audience_id` | string | yes | Format: an audience id: aud_ followed by a 26 character ULID in upper case Crockford base32, such as aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA. |
| `data.previous` | `AudienceCountSummary` or null | yes |  |
| `data.current` | `AudienceCountSummary` | yes | The headline numbers of one count of an audience. |
| `data.diff` | object | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "audience.refreshed",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "audience_id": "aud_01JB4H2K9V6XN3M9R6T1Y5W8ZA",
    "previous": {
      "count_id": "cnt_7K2PQ4MAXN3R6TBY5WDZ",
      "dataset_version": "2026-W39",
      "contacts": 12840,
      "companies": 3115,
      "emailable": 12310,
      "counted_at": "2026-09-26T10:14:00Z"
    },
    "current": {
      "count_id": "cnt_Q3N5X2M7K4P6R2T4V4W6",
      "dataset_version": "2026-W40",
      "contacts": 13054,
      "companies": 3160,
      "emailable": 12520,
      "counted_at": "2026-10-05T06:00:00Z"
    },
    "diff": {
      "contacts": 214,
      "companies": 45,
      "emailable": 210,
      "new_records": 231,
      "removed_records": 17
    }
  }
}
```

## credits.low

The balance crossed the configured threshold.

Payload: `CreditsLowEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `credits.low` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.balance` | number | yes |  |
| `data.threshold` | integer (0 or more) | yes |  |
| `data.top_up_url` | string (uri) | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "credits.low",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "balance": 180,
    "threshold": 200,
    "top_up_url": "https://mydatahub.emailmovers.com/billing/top-up"
  }
}
```

## credits.topped_up

A top-up settled.

Payload: `CreditsToppedUpEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `credits.topped_up` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.credits_added` | integer (0 or more) | yes |  |
| `data.balance` | number | yes |  |
| `data.source` | enum: `credit_pack`, `top_up`, `plan`, `refund`, `adjustment` | yes | credit_pack for credit packs bought by card, top_up for credits bought at the per-contact scale (with or without credit packs), plan for a plan's credits, refund or adjustment. |
| `data.amount_gbp_ex_vat` | number (0 or more) or null | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "credits.topped_up",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "credits_added": 40000,
    "balance": 41200,
    "source": "credit_pack",
    "amount_gbp_ex_vat": 1249
  }
}
```

## warranty.replacement_issued

A bounced or job-changed record was replaced, or re-credited.

Payload: `WarrantyReplacementEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `warranty.replacement_issued` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.retired_record_id` | string | yes | Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8. |
| `data.replacement_record_id` | string or null | yes |  |
| `data.reason` | enum: `hard_bounce`, `job_change` | yes |  |
| `data.credits_refunded` | number (0 or more) | yes | Credits, to one decimal place: whole at 1 credit a record, fractional only where a rule is (0.1 company-only, 1.8 outright, both proposed). |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "warranty.replacement_issued",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "retired_record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
    "replacement_record_id": null,
    "reason": "hard_bounce",
    "credits_refunded": 1
  }
}
```

## suppression.created

An objection reached a record your organisation holds. Suppress it before any send.

Payload: `SuppressionCreatedEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `suppression.created` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | `SuppressionNotice` | yes | A record you hold that is now suppressed, as the suppression.created webhook carries it. |
| `data.record_id` | string | yes | Format: a contact id: contact_ followed by a 26 character ULID in upper case Crockford base32, such as contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8. |
| `data.reason` | enum: `none`, `objected_to_emailmovers`, `objected_to_licensee`, `deceased`, `left_company` | yes |  |
| `data.scope` | enum: `licensee`, `emailmovers` | yes |  |
| `data.suppressed_at` | string (date-time) | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "suppression.created",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
    "reason": "objected_to_emailmovers",
    "scope": "emailmovers",
    "suppressed_at": "2026-10-01T08:30:00Z"
  }
}
```

## push.completed

A CRM push finished.

Payload: `PushCompletedEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `push.completed` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | `Push` | yes | A CRM push or its dry run, such as 4,102 to create and 0 already in your HubSpot. |
| `data.id` | string | yes | Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5. |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `data.dry_run` | boolean | yes |  |
| `data.status` | enum: `queued`, `running`, `completed`, `partial`, `failed` | yes |  |
| `data.summary` | object | yes |  |
| `data.property_group` | array of enum: `mydatahub_record_id`, `licence_expires_at`, `lawful_basis`, `article14_status`, `tps_ctps_screened_at`, `subscriber_class`, `suppression_status`, `compliance_receipt_url` | yes | The MyDataHub property group written on every record (CrmPropertyGroup). |
| `data.sample` | `CrmPropertyGroup` or null | yes | On a dry run: the property group the first record would carry. |
| `data.errors` | array of object | yes |  |
| `data.created_at` | string (date-time) | yes |  |
| `data.completed_at` | string (date-time) or null | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "push.completed",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "id": "psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5",
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
    "dry_run": false,
    "status": "completed",
    "summary": {
      "to_create": 4102,
      "to_update": 0,
      "already_present": 0,
      "skipped": 0,
      "failed": 0
    },
    "property_group": [
      "mydatahub_record_id",
      "licence_expires_at",
      "lawful_basis",
      "article14_status",
      "tps_ctps_screened_at",
      "subscriber_class",
      "suppression_status",
      "compliance_receipt_url"
    ],
    "sample": {
      "mydatahub_record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
      "licence_expires_at": "2027-09-26",
      "lawful_basis": "legitimate_interests_art6_1_f",
      "article14_status": "notified",
      "tps_ctps_screened_at": "2026-09-12",
      "subscriber_class": "ltd",
      "suppression_status": "none",
      "compliance_receipt_url": "https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R"
    },
    "errors": [],
    "created_at": "2026-09-26T10:20:00Z",
    "completed_at": "2026-09-26T10:20:03Z"
  }
}
```

## push.partial

A CRM push partly finished; retry the failed records.

Payload: `PushPartialEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `push.partial` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | `Push` | yes | A CRM push or its dry run, such as 4,102 to create and 0 already in your HubSpot. |
| `data.id` | string | yes | Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5. |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `data.dry_run` | boolean | yes |  |
| `data.status` | enum: `queued`, `running`, `completed`, `partial`, `failed` | yes |  |
| `data.summary` | object | yes |  |
| `data.property_group` | array of enum: `mydatahub_record_id`, `licence_expires_at`, `lawful_basis`, `article14_status`, `tps_ctps_screened_at`, `subscriber_class`, `suppression_status`, `compliance_receipt_url` | yes | The MyDataHub property group written on every record (CrmPropertyGroup). |
| `data.sample` | `CrmPropertyGroup` or null | yes | On a dry run: the property group the first record would carry. |
| `data.errors` | array of object | yes |  |
| `data.created_at` | string (date-time) | yes |  |
| `data.completed_at` | string (date-time) or null | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "push.partial",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "id": "psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5",
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
    "dry_run": false,
    "status": "partial",
    "summary": {
      "to_create": 4090,
      "to_update": 0,
      "already_present": 0,
      "skipped": 0,
      "failed": 12
    },
    "property_group": [
      "mydatahub_record_id",
      "licence_expires_at",
      "lawful_basis",
      "article14_status",
      "tps_ctps_screened_at",
      "subscriber_class",
      "suppression_status",
      "compliance_receipt_url"
    ],
    "sample": {
      "mydatahub_record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
      "licence_expires_at": "2027-09-26",
      "lawful_basis": "legitimate_interests_art6_1_f",
      "article14_status": "notified",
      "tps_ctps_screened_at": "2026-09-12",
      "subscriber_class": "ltd",
      "suppression_status": "none",
      "compliance_receipt_url": "https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R"
    },
    "errors": [],
    "created_at": "2026-09-26T10:20:00Z",
    "completed_at": "2026-09-26T10:20:03Z"
  }
}
```

## push.failed

A CRM push failed.

Payload: `PushFailedEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `push.failed` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | `Push` | yes | A CRM push or its dry run, such as 4,102 to create and 0 already in your HubSpot. |
| `data.id` | string | yes | Format: a CRM push id: psh_ followed by a 26 character ULID in upper case Crockford base32, such as psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5. |
| `data.order_id` | string | yes | Format: an order id: ord_ followed by a 26 character ULID in upper case Crockford base32, such as ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E. |
| `data.connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `data.dry_run` | boolean | yes |  |
| `data.status` | enum: `queued`, `running`, `completed`, `partial`, `failed` | yes |  |
| `data.summary` | object | yes |  |
| `data.property_group` | array of enum: `mydatahub_record_id`, `licence_expires_at`, `lawful_basis`, `article14_status`, `tps_ctps_screened_at`, `subscriber_class`, `suppression_status`, `compliance_receipt_url` | yes | The MyDataHub property group written on every record (CrmPropertyGroup). |
| `data.sample` | `CrmPropertyGroup` or null | yes | On a dry run: the property group the first record would carry. |
| `data.errors` | array of object | yes |  |
| `data.created_at` | string (date-time) | yes |  |
| `data.completed_at` | string (date-time) or null | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "push.failed",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "id": "psh_01JB4NC3W2X6Y7Z8A1B2C3D4E5",
    "order_id": "ord_01JB4N9S3W2X6Y7Z8A1B2C3D4E",
    "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
    "dry_run": false,
    "status": "failed",
    "summary": {
      "to_create": 0,
      "to_update": 0,
      "already_present": 0,
      "skipped": 0,
      "failed": 4102
    },
    "property_group": [
      "mydatahub_record_id",
      "licence_expires_at",
      "lawful_basis",
      "article14_status",
      "tps_ctps_screened_at",
      "subscriber_class",
      "suppression_status",
      "compliance_receipt_url"
    ],
    "sample": {
      "mydatahub_record_id": "contact_01JA9X2V7XN3M9R6T1Y5W8ZAK8",
      "licence_expires_at": "2027-09-26",
      "lawful_basis": "legitimate_interests_art6_1_f",
      "article14_status": "notified",
      "tps_ctps_screened_at": "2026-09-12",
      "subscriber_class": "ltd",
      "suppression_status": "none",
      "compliance_receipt_url": "https://mydatahub.emailmovers.com/receipts/rcpt_01JB4NA2Q5W8ZAK8Q2V7XN3M9R"
    },
    "errors": [],
    "created_at": "2026-09-26T10:20:00Z",
    "completed_at": "2026-09-26T10:20:03Z"
  }
}
```

## connection.expired

A CRM token failed; reconnect in the app.

Payload: `ConnectionExpiredEvent`.

| Member | Type | Always present | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Also sent as the webhook-id header; your idempotency key. |
| `type` | `connection.expired` | yes |  |
| `timestamp` | string (date-time) | yes |  |
| `api_version` | string | yes | A dated MyDataHub-Version, such as 2027-01-15. |
| `data` | object | yes |  |
| `data.connection_id` | string | yes | Format: a CRM connection id: conn_ followed by a 26 character ULID in upper case Crockford base32, such as conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G. |
| `data.provider` | string | yes |  |
| `data.reconnect_url` | string (uri) | yes |  |

```json
{
  "id": "evt_01JB4NB7T1Y5W8ZAK8Q2V7XN3M",
  "type": "connection.expired",
  "timestamp": "2026-09-26T10:16:04Z",
  "api_version": "2027-01-15",
  "data": {
    "connection_id": "conn_01JB3Y2X6Y7Z8A1B2C3D4E5F6G",
    "provider": "hubspot",
    "reconnect_url": "https://mydatahub.emailmovers.com/api-keys"
  }
}
```
