# Invitation lifecycle: created, accepted, revoked

Three events for the invitation object. They describe the invite, not the membership.

## What each means

1. `organizationInvitation.created`: someone sent an invite. Use it to log the invite, notify the invitee through your own channels if you want, or start an expiry timer.
2. `organizationInvitation.accepted`: the invitee accepted. This is NOT the join. The membership is created right after, and `organizationMembership.created` is the event that grants access. If you provision on accepted, you duplicate the membership handler's work and risk double-provisioning.
3. `organizationInvitation.revoked`: the invite was withdrawn before acceptance. Cancel anything you queued on created: expiry timers, reminder emails, pending-seat holds.

## What to do on receipt

1. On created: record the pending invite (email, org, role offered, expiry). Optionally send your own invite email; Clerk may already send one depending on settings, so check before doubling up.
2. On accepted: mark the invite accepted and WAIT for `organizationMembership.created` to grant access. One grant path, owned by the membership event.
3. On revoked: delete the pending invite record and cancel follow-ups.
4. Return 200.

## The trap

Granting access on `accepted`. It feels like the join, but the membership event is the source of truth for "this user is now in this org with this role." Two grant paths means two chances to disagree. Keep exactly one.

## Checklist

- Invites expire. If you run your own expiry, reconcile with Clerk's expiry rather than assuming yours wins.
- A revoked invite followed by a fresh invite to the same email is normal. Key pending invites by invitation id, not by email.
- Do not confuse these with the top-level `invitation.*` events, which are the older non-org invitation object. Know which one your app uses.