group.members_synced

The set of people in a group changed, with the deltas.

project_id is always null for group events.

When this fires

The membership of a group is replaced and the result differs from what was there before — via PUT /v1/groups/{group}/members or the dashboard's group editor.

A group carries permissions, so who is in it decides who can do what. Until this event existed, renaming a group announced itself with group.updated while changing who it granted access to announced nothing at all — an integration mirroring access control could see the label change and miss the membership.

A no-op sync is silent

Syncing the same set that is already there emits nothing. This event means the membership actually moved, so receiving one always implies at least one id in added_ids or removed_ids.

Required ability

group:view — the token must carry this at mint time to subscribe an endpoint to this event.

Payload

{
  "id": "evt_01HX...",
  "type": "group.members_synced",
  "created_at": "2026-08-31T10:05:00Z",
  "api_version": "2026-05-01",
  "project_id": null,
  "data": {
    "group_id": "1f68d92a-04c5-4e83-97b1-3d6a05e2f847",
    "team_id": "a83f0d51-4c92-4b7e-8615-2fd9e70a3c86",
    "code": "core-community",
    "name": "Core Community",
    "member_ids": [
      "2a91c4e7-6f38-4b52-8e0d-9c1a7b3f5d80",
      "6f9b2e37-c184-4a05-8d72-30e16bc9f458"
    ],
    "added_ids": ["6f9b2e37-c184-4a05-8d72-30e16bc9f458"],
    "removed_ids": ["d05e1a83-7c46-4f29-b613-8ae407c9d251"],
    "member_count": 2
  }
}

Field reference

FieldTypeNotes
idstring ULIDUnique event id, prefixed evt_. Use for idempotent processing.
typestringAlways group.members_synced for this event.
created_atISO 8601 timestampServer-side emission time.
api_versionstringWebhook API contract version.
project_idnullAlways null — account-level event.
data.group_idstring UUIDThe group whose membership changed.
data.team_idstring UUIDTeam that owns the group.
data.codestringStable group code, immutable across renames.
data.namestringDisplay name at the time of the sync.
data.member_idsarray of UUIDThe membership after the sync — the complete list, not a page of it.
data.added_idsarray of UUIDUsers who gained the group in this sync. Possibly empty.
data.removed_idsarray of UUIDUsers who lost it. Possibly empty.
data.member_countintegermember_ids.length, carried so a consumer can size the change without parsing the array.

Why the deltas are in the payload

An access-control mirror needs to know who lost the group, and diffing two snapshots on the far side is work it should not have to do — it requires having stored the previous state, and gets the answer wrong the first time it sees a group. added_ids and removed_ids are computed where the truth is, from the membership read immediately before the write and the list written immediately after it.

member_ids is still there for consumers that would rather replace their copy than apply a patch. Both are consistent with each other.

Caveats

  • This is a replace, not an add. Anyone absent from the request is removed. An empty list empties the group.
  • Permissions are not in the payload. What the group grants is a property of the group, not of this event — read it from GET /v1/groups/{group} if your mirror needs the effective permission set.
  • Users, not projects. A group clusters collaborators. It is not a way to bundle projects.
  • Removal from a group does not remove anyone from the team.

How is this guide?

On this page

Subscriby is a product designed by you — for you.

No boardroom full of executives deciding what we ships next. Our roadmap always shaped by you with your feedback.

Share feedback or a request