---
title: "The set of people in a group changed, with the deltas."
method: POST
path: "group.members_synced"
tags: ["group.* events"]
---

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

`POST group.members_synced` (webhook)

### When this fires

The membership of a group is replaced and the result differs from what was there before, via [the group members endpoint](https://docs.subscriby.net/api/v1/reference/groups) 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`.

### 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 [the group endpoint](https://docs.subscriby.net/api/v1/reference/groups) 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.

### Related events

- `group.updated`: the group was renamed or re-permissioned.
- `group.created`: predecessor.
- `group.deleted`: terminal state; everyone loses what it granted at once.

## Headers

- `SB-Signature` string, required
- `SB-Event-Id` string, required
- `SB-Event-Name` string, required
- `Content-Type` string, required
- `User-Agent` string, required

## Payload

- object: The envelope every event is delivered in.
  - `id` string, required: A ULID unique to the event, prefixed `evt_`. Every retry carries the same id, so it is the key to deduplicate on.
  - `type` 'group.members_synced', required: The event name, always `group.members_synced` here.
  - `created_at` string, date-time, required: When the event was emitted, server time, ISO 8601.
  - `api_version` string, required: The contract version of the payload shape, `2026-05-01` today. A breaking change to a shape ships under a new version.
  - `project_id` string, uuid, nullable, required: The project the event belongs to; null for team-scoped and billing events.
  - `data` object, required: The event-specific payload. Keys that do not apply to a given emission are omitted rather than sent as null, so check for presence.
    - `group_id` string, uuid, required: The group whose membership changed.
    - `team_id` string, uuid, required: Team that owns the group.
    - `code` string, required: Stable group code, immutable across renames.
    - `name` string, required: Display name at the time of the sync.
    - `member_ids` string[], required: The membership **after** the sync: the complete list, not a page of it.
    - `added_ids` string[], required: Users who gained the group in this sync. Possibly empty.
    - `removed_ids` string[], required: Users who lost it. Possibly empty.
    - `member_count` integer, required: `member_ids.length`, carried so a consumer can size the change without parsing the array.

## Acknowledgement `2XX`

Your endpoint acknowledged the delivery. Any 2xx status within 30 seconds marks it delivered; the response body is ignored.

## Other responses

- `default`: Any other status, a connection failure, or no answer within 30 seconds counts as a failed attempt. The delivery is retried 8 times, after 10 seconds, 30 seconds, 2 minutes, 10 minutes, 1 hour, 6 hours, 1 day, 3 days; the last failure dead-letters it, and it can be retried from the dashboard or `POST /v1/webhook-deliveries/{delivery}/retry`. After 20 consecutive failures the endpoint is paused until it is resumed.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
