Self verification backend + Discord bot using Self mock passports.
This project exposes a Self-powered backend and a Discord bot that:
- Generates a Self QR code and sends it to users via DM (
/verifyslash command). - Verifies proofs on an Express backend with mock passports and OFAC checking.
- Automatically assigns a “verified” role so users can see age-gated channels.
- Logs all verification activity and stores QR images on disk.
-
Express backend
- Uses
SelfBackendVerifierfrom@selfxyz/coreto verify proofs from the Self app. - Configured for staging / mock passports with OFAC enabled and a minimum age requirement.
- Exposes:
POST /api/verify– main verification endpoint (called by the Self app).
- Uses
-
Discord bot
- Listens for
/verifyin your Discord server. - For each user:
- Builds a Self app configuration (
SelfAppBuilderfrom@selfxyz/common) including a unique session id and Discord user id. - Generates a Self universal link and encodes it as a QR PNG.
- DMs the QR to the user.
- Builds a Self app configuration (
- When the Self app completes verification:
- The Self backend decodes the
userDefinedDatafrom the proof. - Matches it to a pending Discord session.
- Assigns a configurable
Verifiedrole to the user. - Sends them a success DM.
- The Self backend decodes the
- Listens for
-- Data & logging
- QR images:
server/qrcodes/*.png - Logs:
server/logs/discord-verifier.log(JSON lines; safe to tail/parse).
You’ll need:
- Node.js 18+ (Self SDKs recommend Node 22; you may see engine warnings on other versions, but this project runs on Node 18–20 as well).
- npm (comes with Node).
- ngrok (or any HTTPS tunnelling tool):
- Required because the Self app must reach your
/api/verifyendpoint over HTTPS with a public URL.
- Required because the Self app must reach your
- A Discord account with permission to:
- Create applications in the Discord Developer Portal.
- Add a bot to your server and manage roles.
cd server
npm installThis installs Express, Discord.js, the Self SDKs, and other dependencies.
-
Go to https://ngrok.com and create a free account.
-
Install the ngrok CLI (follow their OS-specific instructions).
-
Get your ngrok authtoken from the ngrok dashboard.
-
Configure ngrok locally:
ngrok config add-authtoken <YOUR_NGROK_AUTHTOKEN>
-
Once the backend is running (see section 6), expose it:
ngrok http 3001
-
ngrok will show something like:
Forwarding https://40345855ba7b.ngrok-free.app -> http://localhost:3001 -
Construct your Self endpoint URL:
SELF_ENDPOINT = https://40345855ba7b.ngrok-free.app/api/verifyUse this value in your
.envfile (see below).
Important: Self endpoints must be HTTPS and must not be
localhostor127.0.0.1. A tunnel like ngrok solves this.
- Open the Discord Developer Portal: https://discord.com/developers/applications.
- Click “New Application”, give it a name (e.g.
Self Verification Bot), click Create. - On the application’s General Information page:
- Copy Application ID → this will be
DISCORD_CLIENT_ID.
- Copy Application ID → this will be
- Go to the Bot tab:
- Click “Add Bot” → Yes, do it!
- Under Token:
- Click Reset Token, copy the bot token.
- This will be
DISCORD_BOT_TOKEN.
- Scroll down to Privileged Gateway Intents and enable:
SERVER MEMBERS INTENTMESSAGE CONTENT INTENT
- Click Save Changes.
- In the Developer Portal, go to OAuth2 → URL Generator.
- Under Scopes, select:
botapplications.commands
- Under Bot Permissions, select at least:
View ChannelsSend MessagesSend Messages in ThreadsRead Message HistoryManage Roles(required to assign yourVerifiedrole)
- Copy the generated URL and open it in your browser.
- Choose the server where you want the bot to live, click Authorize, and complete the captcha.
- In the Discord client, go to Settings → Advanced.
- Enable Developer Mode.
- Right-click your server icon → Copy Server ID.
- This value is
DISCORD_GUILD_ID.
-
In your server, open Server Settings → Roles.
-
Click Create Role:
- Name it something like
Verified. - Give it a color if you want.
- Save changes.
- Name it something like
-
To get the role ID:
- Right-click the
Verifiedrole → Copy Role ID. - This value is
DISCORD_VERIFIED_ROLE_ID.
- Right-click the
-
Configure the restricted category:
- Create a category (e.g.
restricted). - Create one or more channels inside it.
- Right-click the category → Edit Category → Permissions:
- For
@everyone: disableView Channel. - For
Verifiedrole: enableView Channel.
- For
- Save changes.
- Create a category (e.g.
Make sure the bot’s own role is above the
Verifiedrole in the role list, otherwise it cannot assign that role.
Create a file server/.env (this file is ignored by git; safe to keep secrets there).
Example:
PORT=3001
# Self verifier configuration
SELF_SCOPE=demo-scope
SELF_ENDPOINT=https://your-ngrok-id.ngrok-free.app/api/verify
# Discord bot configuration
DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_CLIENT_ID=your-discord-application-client-id
DISCORD_GUILD_ID=your-discord-server-id
DISCORD_VERIFIED_ROLE_ID=verified-role-id
# Optional: customize how the Self app appears
SELF_APP_NAME=Self Discord Verification (Mock Passports)
SELF_LOGO_URL=https://i.postimg.cc/mrmVf9hm/self.pngField-by-field:
PORT- Local port for Express (default
3001).
- Local port for Express (default
SELF_SCOPE- A short ASCII string identifying this verification “scope”, e.g.
demo-scope. - Must be ≤ 31 characters.
- A short ASCII string identifying this verification “scope”, e.g.
SELF_ENDPOINT- Public HTTPS URL for
/api/verify, e.g.https://40345855ba7b.ngrok-free.app/api/verify. - Must not contain
localhostor127.0.0.1.
- Public HTTPS URL for
DISCORD_BOT_TOKEN- Bot token from the Developer Portal Bot tab.
DISCORD_CLIENT_ID- Application ID from the Developer Portal General Information page.
DISCORD_GUILD_ID- Your Discord server (guild) ID.
DISCORD_VERIFIED_ROLE_ID- ID of the role that should be assigned when verification succeeds; used to gate the restricted category.
SELF_APP_NAME/SELF_LOGO_URL- Optional customizations for how the Self app entry appears inside the Self mobile app.
-
Start the backend + bot
cd server npm run devYou should see logs like:
Self Express Backend listening on http://localhost:3001discord.commands_registered(slash commands registered)discord.ready(bot logged in)
-
Expose the backend to Self (ngrok)
With the backend running:
ngrok http 3001
Update
SELF_ENDPOINTif the ngrok URL changes. -
Endpoints
POST /api/verify- Called by the Self app.
- Uses
SelfBackendVerifierto validate the proof against your config (age, OFAC, etc.).
-
In your Discord server, in any text channel, run:
/verify -
The bot:
- Immediately replies in the channel (ephemeral):
- “Generating your Self verification QR… I’ll DM it to you shortly.”
- Generates a unique Self app configuration for you.
- Creates a QR PNG and saves it to
server/qrcodes/.... - DMs you the QR with the instruction:
- “Scan this QR code with the Self app (staging/mock passports) to verify your age/identity.”
- Immediately replies in the channel (ephemeral):
-
You:
- Open the Self app (staging) on your phone.
- Ensure you have a mock passport set up.
- Scan the QR and follow the flow inside the Self app.
-
The backend:
-
Verifies the proof (age, OFAC, etc.) via
SelfBackendVerifier. -
Decodes the
userDefinedDatafrom the proof to find the matching Discord session. -
Assigns the
Verifiedrole to your user. -
Logs the event to
server/logs/discord-verifier.log. -
DMs you:
✅ Your Self verification succeeded. You now have access to the restricted channels.
-
-
You now see the restricted category/channels where you granted
View Channelto theVerifiedrole.
No /verify command in my server
- Check the logs for
discord.commands_registered. - Ensure:
DISCORD_CLIENT_ID,DISCORD_GUILD_ID,DISCORD_BOT_TOKENare set correctly.- The bot is actually added to that server.
- You restarted the backend after setting
.env.
Bot says it sent a DM, but I don’t see it
- In Discord:
Settings → Privacy & Safety → Server Privacy Defaultsand enable “Allow direct messages from server members”.- Or right-click your server → Privacy Settings → enable DMs.
- Try
/verifyagain.
User verifies but doesn’t get the role
- Check
server/logs/discord-verifier.logfor:verification.role_not_found–DISCORD_VERIFIED_ROLE_IDmight be wrong or the role was deleted.verification.no_role_configured– you didn’t setDISCORD_VERIFIED_ROLE_ID.verification.discord_error– bot lacksManage Rolesor its role is below the target role.
- Ensure:
- Bot has the
Manage Rolespermission. - Bot’s role is above the
Verifiedrole in the role list.
- Bot has the
Self app errors about endpoint or scope
- Confirm:
SELF_SCOPEis ≤ 31 ASCII characters.SELF_ENDPOINTis exactly the ngrok URL +/api/verifyand does not containlocalhost.- ngrok is running and points to the same port/host where Express is listening.
