# ICS Push Notifications

> **PRERELEASE**

When creating or updating a partner event, the `event_invite_mechanism` parameter controls how attendees are notified.
When unspecified, the default mechanism is used - this is by pushing an event directly into the calendar for Read-Write Calendar Access Mode connections, or via an email invite for Free/Busy Only and No Connection Calendar Access Modes.

The `subscriptions` mechanism instead delivers a representation of the event and Smart Invite ICS payloads via a webhook subscription. This lets you take full control of how the invite is presented and which medium you use to send it.

The ICS content is generated using the same logic as a `smart_invite`, so the `METHOD`, attendees, organizer, and reminders are identical to what would be produced by that endpoint, so you can [refer to those docs for more details](/developers/smart-invites/index.md).

The feature has two halves which work together:

- `event_invite_mechanism: { &quot;type&quot;: &quot;subscriptions&quot; }` on the event describes *which* mechanism is responsible for delivering the invite (over a webhook subscription, or the default where Cronofy handle adding directly into a Read-Write calendar or by invite email for Free/Busy calendars).

- A subscription with an `event_invite_updates` interaction tells Cronofy *where* to deliver the invite (which URI accepts the callback).

Both halves must be present on the event for an invite to be pushed to the subscription.

#### Receiving invites via webhook subscription
- Set `event_invite_mechanism` to `{ &quot;type&quot;: &quot;subscriptions&quot; }` on the [Create or Update Event](/developers/api/events/upsert-event/index.md) request

- Add a subscription with an `event_invite_updates` interaction:

```http
POST /v1/calendars/cal_n23kjnwrw2_jsdfjksn234/events HTTP/1.1
Host: {data_center_url}
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json; charset=utf-8

{
  "event_id": "qTtZdczOccgaPncGJaCiLg",
  "summary": "Board meeting",
  "description": "Discuss plans for the next quarter.",
  "start": "2026-07-28T15:30:00Z",
  "end": "2026-07-28T17:00:00Z",
  "tzid": "Etc/UTC",
  "event_invite_mechanism": { "type": "subscriptions" },
  "attendees": {
    "invite": [
      { "email": "attendee@example.com", "display_name": "Example Attendee" }
    ]
  },
  "subscriptions": [
    {
      "type": "webhook",
      "uri": "https://example.com/cronofy/event_invite_updates",
      "interactions": [
        { "type": "event_invite_updates" }
      ]
    }
  ]
}
```

#### Request parameters
##### `event_invite_mechanism` *(optional)* **ALPHA**

An object controlling how attendees are notified about the event. The object is open-ended so additional configuration can be added in future without breaking integrators.

##### `event_invite_mechanism.type` *(required)* **ALPHA**

A [`String`](/developers/api/data-types/index.md) selecting the delivery mechanism. Supported values:

- `default` — Put an event directly in the calendar if connected with Write permissions, else send an email with an invite attached. This is the existing behaviour and the default when `event_invite_mechanism` is omitted.

- `smart_invite` — Email a Smart Invite to the attendees. No event is written directly into the calendar, event if Write permissions have been granted. The recipient must accept the invite to add it to their calendar.

- `subscriptions` — POST Smart Invite ICS payloads to subscriptions for the `event_invite_updates` interaction. The Push Notification includes an ICS is identical to the one the `smart_invite` mechanism would email. No email is sent or event is written directly into the calendar, it is the receiving apps responsibility to send.

If `event_invite_mechanism` is omitted entirely, the `default` mechanism is used.

##### `subscriptions.interactions` *(optional)* **ALPHA**

An array of interaction objects used to route notifications. The array itself is optional, in line with [other event subscriptions](/developers/api/conferencing-services/subscriptions/index.md), but to receive a subscription invite at least one subscription on the event must include an entry with `type` set to `event_invite_updates`.

When `event_invite_mechanism.type` is `subscriptions`, every request that sets the mechanism must also include a subscription with an `event_invite_updates` interaction. Requests that do not are rejected with a `422` response. See [Validation](#validation) below.

Subscriptions without an `event_invite_updates` interaction are unaffected by the `subscriptions` mechanism and continue to behave as before.

##### `subscriptions.interactions.type` *(optional)* **ALPHA**

The [`String`](/developers/api/data-types/index.md) identifying the interaction. For subscription invites the value is `event_invite_updates`.

#### Example callback
For each subscription with an `event_invite_updates` interaction, Cronofy will POST a JSON payload containing the event details and an `event_invites` array. Each entry in `event_invites` bundles one or more attendees who should receive the same ICS file. Active recipients on a create or update typically share a single `METHOD:REQUEST` ICS (one entry, many attendees), while an attendee who has just been removed from the event receives a dedicated `METHOD:CANCEL` ICS (its own entry):

```http
POST /cronofy/event_invite_updates HTTP/1.1
Host: example.com
Content-Type: application/json; charset=utf-8
Cronofy-HMAC-SHA256: {Base64(HmacSHA256(body_bytes, CLIENT_SECRET))}

{
  "notification": {
    "type": "event_subscription",
    "interactions": [
      { "type": "event_invite_updates" }
    ]
  },
  "event": {
    "calendar_id": "cal_n23kjnwrw2_jsdfjksn234",
    "event_id": "qTtZdczOccgaPncGJaCiLg",
    "event_uid": "evt_partner_60ba0f2968ed0f6e66e1ab68",
    "summary": "Board meeting",
    "description": "Discuss plans for the next quarter.",
    "start": "2026-07-28T15:30:00Z",
    "end": "2026-07-28T17:00:00Z",
    "deleted": false,
    "attendees": [
      { "email": "attendee@example.com", "display_name": "Example Attendee", "status": "needs_action" }
    ],
    "organizer": {
      "email": "host@example.com",
      "display_name": "Event Host"
    },
    "transparency": "opaque",
    "status": "confirmed"
  },
  "event_invites": [
    {
      "attendees": [
        { "email": "host@example.com", "display_name": "Event Host" },
        { "email": "attendee@example.com", "display_name": "Example Attendee" }
      ],
      "icalendar_operation": "request",
      "icalendar": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nMETHOD:REQUEST\r\n..."
    },
    {
      "attendees": [
        { "email": "removed@example.com" }
      ],
      "icalendar_operation": "cancel",
      "icalendar": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nMETHOD:CANCEL\r\n..."
    }
  ]
}
```

When the partner event itself is deleted, `event.deleted` is `true` and every entry in `event_invites` is a `METHOD:CANCEL` so that all attendees, including the host, can be sent a cancellation:

```http
POST /cronofy/event_invite_updates HTTP/1.1
Host: example.com
Content-Type: application/json; charset=utf-8
Cronofy-HMAC-SHA256: {Base64(HmacSHA256(body_bytes, CLIENT_SECRET))}

{
  "notification": {
    "type": "event_subscription",
    "interactions": [
      { "type": "event_invite_updates" }
    ]
  },
  "event": {
    "calendar_id": "cal_n23kjnwrw2_jsdfjksn234",
    "event_id": "qTtZdczOccgaPncGJaCiLg",
    "event_uid": "evt_partner_60ba0f2968ed0f6e66e1ab68",
    "summary": "Board meeting",
    "description": "Discuss plans for the next quarter.",
    "start": "2026-07-28T15:30:00Z",
    "end": "2026-07-28T17:00:00Z",
    "deleted": true,
    "attendees": [
      { "email": "attendee@example.com", "display_name": "Example Attendee", "status": "cancelled" }
    ],
    "organizer": {
      "email": "host@example.com",
      "display_name": "Event Host"
    },
    "transparency": "opaque",
    "status": "cancelled"
  },
  "event_invites": [
    {
      "attendees": [
        { "email": "host@example.com", "display_name": "Event Host" },
        { "email": "attendee@example.com", "display_name": "Example Attendee" }
      ],
      "icalendar_operation": "cancel",
      "icalendar": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nMETHOD:CANCEL\r\n..."
    }
  ]
}
```

#### Callback parameters
##### `notification.type`

Always `event_subscription`. This matches the envelope used by sibling subscriptions such as [conferencing services](/developers/api/conferencing-services/subscriptions/index.md), [meeting agents](/developers/api/meeting-agents/callbacks/index.md), and [event triggers](/developers/api/event-triggers/index.md).

##### `notification.interactions`

An array containing the interaction(s) this callback was delivered for. For subscription invites the array contains a single entry with `type` set to `event_invite_updates`.

##### `event`

Details of the partner event for which the invite has been generated. The fields present map to those documented for our [Read Events API](/developers/api/events/read-events/index.md). The `event.deleted` field indicates whether the event itself has been deleted; per-attendee outcomes are reported on each entry in `event_invites`.

The `event.organizer` is the account whose `access_token` authenticated the Create or Update Event request. This account is the host of the event and is the `ORGANIZER` in the generated ICS, so it also appears in the `event_invites` bundles.

When the event has been deleted, each `event.attendees[].status` is `cancelled`. This value is specific to this callback and extends the participation statuses documented for the Read Events API.

##### `event_invites`

An array of invite envelopes. Each entry groups one or more attendees who should receive the same ICS file together with the operation that ICS represents. A typical update produces one entry for the bulk `METHOD:REQUEST` (all active attendees and the host) plus one additional entry per attendee removed by the update (each with their own `METHOD:CANCEL`). When the event itself is deleted, every entry is a `METHOD:CANCEL`.

##### `event_invites[].attendees`

The attendees this invite is intended for. Each entry has an `email` and (where known) a `display_name`. When the array contains more than one attendee, every recipient should be sent the same `icalendar` — your application can choose whether to send a single email with multiple `To`/`Cc` addresses or separate emails with the same body.

##### `event_invites[].icalendar_operation`

The action this invite represents, surfaced so receivers can route REQUEST vs CANCEL without parsing the ICS payload.

- `request` - the attached `icalendar` is a `METHOD:REQUEST` (new invitation or update for active attendees).

- `cancel` - the attached `icalendar` is a `METHOD:CANCEL` (the partner event has been deleted, or these specific attendees have been removed from the event).

The value matches the `METHOD` line inside the `icalendar` string.

##### `event_invites[].icalendar`

The raw ICS [`String`](/developers/api/data-types/index.md) for the bundled attendees. This is the payload you would attach to an email if you were sending the invite yourself.

#### Callback headers
##### `Cronofy-HMAC-SHA256`

Can optionally be used to verify that the notification was sent by Cronofy.

This [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) uses the SHA256 algorithm, keyed with the application's client secret, to generate a Base64 encoded hash of the request body.

When an application has multiple active client secrets, a value is generated for each active secret, separated by commas. For example:

```
Cronofy-HMAC-SHA256: {HMAC_FROM_SECRET_1},{HMAC_FROM_SECRET_2}
```

Examples are available in the [cronofy/notification-hmac-examples](https://github.com/cronofy/notification-hmac-examples) GitHub repository.
HMACs are generated in the same way for all callback events.

#### Validation
The wiring between `event_invite_mechanism` and the `event_invite_updates` interaction is validated up front:

- Every request that sets `event_invite_mechanism.type` to `subscriptions` must include at least one subscription with an `event_invite_updates` interaction. Requests that fail this check are rejected with a `422` response and an `event_invite_mechanism: event_invite_updates_subscription_required` error.

- Updates that do not set `event_invite_mechanism` are not affected by this check, but if no matching subscription is present at delivery time the notification is dropped; no callback is sent and no email is dispatched.

- Setting `event_invite_mechanism.type` to a value other than `subscriptions` leaves subscriptions with `event_invite_updates` interactions in place but unused.

#### Delivery behaviour
- Deliveries are debounced. Rapid updates to the same partner event are coalesced into a single notification.

- Each notification is POSTed independently to every matching subscription URI. A non-`2xx` response from one URI does not prevent delivery to the others.

- To be considered successfully delivered, your application must respond in the `2xx` range of HTTP status codes within 7 seconds.

- Failed deliveries are retried with exponential backoff.



---
[Read in HTML](/developers/api-alpha/ics-push-notifications/)