Invitations & Notifications
Fabric supports a complete invitation workflow: create invitations, send email notifications, verify tokens, and automatically create memberships on accept.
Invitation Lifecycle
Section titled “Invitation Lifecycle”POST /v1/invitations (Admin+ creates invitation) | vEmail sent via Resend (outbox poller delivers)Webhook fired to org URL (if configured) | vGET /v1/invitations/{id}/accept?token=xxx (invitee clicks link) | vMembership created (invitee joins org with invited role)Redirect to FABRIC_INVITE_REDIRECT_URLCreating Invitations
Section titled “Creating Invitations”Requires Admin role or higher in the target organization.
curl -X POST https://gofabric.dev/v1/invitations \ -H 'Authorization: Bearer fab_xxx' \ -H 'content-type: application/json' \ -d '{ "organization_id": "<org-id>", "email": "alice@example.com", "role": "member" }'const invitation = await fabric.post("/v1/invitations", { organization_id: "<org-id>", email: "alice@example.com", role: "member",});invitation = fabric.create_invitation( org_id="<org-id>", email="alice@example.com", role="member",)Response:
{ "meta": { "status": 201 }, "data": { "id": "8d464c13-...", "organization_id": "c1ed629e-...", "email": "alice@example.com", "role": "member", "status": "pending", "expires_at": "2026-04-09T12:00:00Z" }}Guards
Section titled “Guards”| Guard | HTTP Status | Description |
|---|---|---|
| Duplicate email | 409 Conflict | A pending invitation already exists for this email in this org |
| Max pending | 429 Too Many Requests | Organization has reached max_pending_invitations limit |
| Max per hour | 429 Too Many Requests | Organization has reached max_invitations_per_hour limit |
| Insufficient role | 403 Forbidden | Caller lacks Admin+ role or explicit ACL grant |
Rate limits are configurable per-org via Organization Settings.
Accepting Invitations
Section titled “Accepting Invitations”The accept endpoint is a GET (not POST) so it works as a clickable email link:
GET /v1/invitations/{id}/accept?token={raw_token}Security:
- The
tokenis a 32-byte random value generated at invitation creation - Only the BLAKE3 keyed hash is stored in the database
- Verification uses constant-time comparison to prevent timing attacks
- The server-side key is configured via
FABRIC_INVITE_TOKEN_KEY
On success:
- Invitation status transitions to
accepted - A Membership is created linking the invitee to the organization with the invited role
- If
FABRIC_INVITE_REDIRECT_URLis set, redirects to:{url}?status=accepted&org_id={org_id}&invitation_id={id} - Otherwise returns a JSON response
On failure:
- Invalid/missing token:
?status=error&reason=invalid_token - Already accepted:
?status=error&reason=already_accepted - Expired:
?status=error&reason=expired
Listing & Fetching Invitations
Section titled “Listing & Fetching Invitations”# List all invitations for an orgcurl 'https://gofabric.dev/v1/invitations?organization_id=<org-id>' \ -H 'Authorization: Bearer fab_xxx'
# Get a single invitationcurl https://gofabric.dev/v1/invitations/<invitation-id> \ -H 'Authorization: Bearer fab_xxx'// List invitationsconst invitations = await fabric.get("/v1/invitations", { params: { organization_id: "<org-id>" },});
// Get single invitationconst inv = await fabric.get("/v1/invitations/<invitation-id>");Revoking Invitations
Section titled “Revoking Invitations”curl -X POST https://gofabric.dev/v1/invitations/<id>/revoke \ -H 'Authorization: Bearer fab_xxx'Revoked invitations cannot be accepted.
Expiration
Section titled “Expiration”Invitations expire after 7 days. A background job runs every 10 minutes to mark expired invitations. Expired invitations cannot be accepted — the inviter must create a new one.
Email Notifications
Section titled “Email Notifications”When an invitation is created, Fabric queues an email notification via a transactional outbox:
- An outbox entry is written to the database in the same request
- A background poller (every 10 seconds) claims pending entries and sends them via Resend
- Failed sends are retried with exponential backoff: 1 minute, 5 minutes, 30 minutes (3 attempts max)
Email Template
Section titled “Email Template”The invitation email includes:
- Organization display name (configurable via org settings)
- Invited role
- A clickable “Accept Invitation” button linking to the Fabric accept endpoint
- Expiry notice (7 days)
Configuration
Section titled “Configuration”| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | Yes | Resend API key (starts with re_) |
FABRIC_EMAIL_FROM | No | Sender address (default: noreply@gofabric.dev) |
FABRIC_INVITE_TOKEN_KEY | No | 32-byte hex key for token hashing (auto-generated if unset) |
FABRIC_INVITE_REDIRECT_URL | No | URL to redirect to after accept |
Domain verification: Add your sending domain to Resend and configure the required DNS records (DKIM, SPF, DMARC).
Webhook Notifications
Section titled “Webhook Notifications”Invitation events (invitation.created, invitation.accepted, invitation.revoked) are delivered automatically to any webhook subscriptions registered for the organization. Register a webhook filtered on these events to receive push notifications:
await fabric.webhooks.create(orgId, { url: "https://your-app.com/webhooks/fabric", secret: process.env.FABRIC_WEBHOOK_SECRET, event_filter: ["invitation.created", "invitation.accepted", "invitation.revoked"],});See the Webhooks Reference for signature verification, delivery semantics, and the full event catalog.
Organization Settings
Section titled “Organization Settings”Per-org configuration for branding and rate limits:
# Get settings (returns defaults if none configured)curl https://gofabric.dev/v1/organizations/<org-id>/settings \ -H 'Authorization: Bearer fab_xxx'
# Update settingscurl -X PUT https://gofabric.dev/v1/organizations/<org-id>/settings \ -H 'Authorization: Bearer fab_xxx' \ -H 'content-type: application/json' \ -d '{ "display_name": "Acme Corp", "logo_url": "https://acme.com/logo.png", "email_from_name": "Acme Corp via Fabric", "max_pending_invitations": 50, "max_invitations_per_hour": 10 }'Settings Fields
Section titled “Settings Fields”| Field | Default | Description |
|---|---|---|
display_name | org slug | Name shown in invitation emails |
logo_url | none | Logo URL for email headers |
email_from_name | none | Custom “From” name (e.g., “Acme Corp via Fabric”) |
max_pending_invitations | 100 | Max concurrent pending invitations per org |
max_invitations_per_hour | 20 | Max new invitations created per hour per org |
API Endpoints Summary
Section titled “API Endpoints Summary”| Method | Path | Description |
|---|---|---|
| POST | /v1/invitations | Create invitation (Admin+) |
| GET | /v1/invitations | List invitations (requires organization_id query param) |
| GET | /v1/invitations/{id} | Get invitation by ID |
| GET | /v1/invitations/{id}/accept | Accept invitation (token required) |
| POST | /v1/invitations/{id}/revoke | Revoke invitation |
| GET | /v1/organizations/{id}/settings | Get org settings |
| PUT | /v1/organizations/{id}/settings | Update org settings (Owner) |