Invitations & Webhooks
Invitations
Invitations are webhook-only — this template does not send emails. When an admin invites someone, the app creates an invitations row and fires an invitation.created webhook to the team's configured webhook_url (set in Settings → General). The webhook payload includes the invite_url, and it is up to the receiving system to deliver it (email, Slack, internal tool, etc.).
If no webhook_url is configured, the invitation is still created and the admin can copy the invite link manually from Settings → Members → Pending invitations → "Copy invite link". Nothing is ever sent automatically.
Adding email delivery later
If you want to send invitation emails directly from the app instead of via the webhook, add your provider (Resend, Postmark, etc.) in server/api/teams/[teamId]/invitations/index.post.ts alongside the fireWebhook call. The invite URL is already constructed there — just reuse it.
Webhooks
All team-scoped mutations fire a webhook. Payloads always include event, team_id, team_name, and timestamp; event-specific fields are listed below.
| Event | Fired when | Extra fields |
|---|---|---|
invitation.created | Admin invites someone | email, role, invite_url, invited_by |
invitation.accepted | Invitee accepts the invitation | email, role, member_name |
invitation.revoked | Admin revokes a pending invitation | email, role, revoked_by |
member.removed | Admin removes a member | member_email, member_name, removed_by |
member.role_changed | Admin changes a member's role | member_email, member_name, old_role, new_role, changed_by |
team.updated | Team settings are saved | changes (object of updated fields), updated_by |
team.deleted | Owner deletes a team | deleted_by |
Webhook delivery is fire-and-forget: failures are silently ignored and do not block the originating request. If you need retries or delivery guarantees, replace server/utils/fireWebhook.ts with a queue-backed implementation.