Bot that gates access to Discord channels and Slack workspaces using LinkedTrust credentials and endorsements.
A user runs the /verify slash command with their identifier (GitHub URL, LinkedIn URL, DID, etc.). The bot:
- Resolves all linked identities via the LinkedTrust Identity API
- Checks if any of those identities have a valid credential
- Checks if any of those identities have been endorsed by an approved endorser
- If either check passes, the user is granted access; otherwise, access is denied
Access is granted if either condition is met:
- The user has any valid credential (optionally filtered by issuer and/or schema)
- The user has been endorsed by an approved endorser (e.g., a team lead)
GET /api/identity/subjects?uri=<URI>— resolve all linked identities for a URIGET /api/credentials?subject=<URI>— check for credentials (supportsissuerandschemafilters)GET /api/claims?subject=<URI>&claim=ENDORSES— check for endorsement claims
- Node.js 18+
- A LinkedTrust backend instance (default:
https://dev.linkedtrust.us)
npm installCreate .env file. you can ask for the env vars from Tuna
The bot starts whichever platforms have tokens configured. You can run Discord only, Slack only(not working yet), or both.
# Development (no build step)
npm run dev
# Production
npm run build
npm start- Uses slash commands (
/verify,/gate-status) - On success: assigns the gated role to the user
- On denial: sends an ephemeral message explaining why
- Requires bot permissions:
Manage Roles,Send Messages,Use Slash Commands - Requires the bot's role to be above the gated role in the server's role hierarchy
- Uses slash commands (
/verify-self,/gate-status) - Connects via Socket Mode (no public URL needed)
- On success: invites the user to the gated private channel
- On denial: sends a DM explaining why
- Required bot scopes:
chat:write,channels:manage,groups:write,commands,users:read
src/
app.ts # Entrypoint — starts configured platforms
config.ts # Reads .env, platform-aware config
gate/
check.ts # Platform-agnostic gate logic (checkGate)
linkedtrust.ts # LinkedTrust API client
platforms/
discord/index.ts # Discord slash command handlers
slack/index.ts # Slack slash command handlers
The gate logic in src/gate/ is shared across all platforms. Adding a new platform means adding a new directory under src/platforms/ with no changes to the gate code.
- Create
src/platforms/<name>/index.tsexporting an asyncstart<Name>()function - Add platform-specific env vars to
config.ts - Import and call
start<Name>()inapp.ts - Use
checkGate()fromsrc/gate/check.ts— it returns aGateResultwithgranted,reason, andidentities