Connect Discord and X
Connect your Discord server so quests can check who joined, reward messages and reactions, and hand out roles. Check follows, posts and profiles on X.
Your community already talks on Discord and X. Connect them, and quests can check what members do there. For example:
- "Join our Discord server", optionally with a required role.
- "React 🫡 to our announcements", paid automatically.
- "Post in #show-and-tell", once a day.
- "Get a ❤ from a moderator", which pays the member who wrote the message.
- "Finish onboarding and get the Contributor role" in Discord.
- "@ana got +10 XP from @sam", posted in a channel each time a quest pays.
- "Follow us on X", "Repost our launch", or "Put our tag in your bio".
Connect Discord once per project. Quests then use that server, so nobody copies server IDs or bot tokens into code. X needs no setup in your project at all.
Telegram groups and channels connect from the same Integrations page; see Telegram.
Each service is a card on Integrations with its status and what it needs from you next, such as Connect or Manage. Open a card to see its details and act on it in a panel beside the page.
Other services Domino integrates appear on the same page, one card each, such as Klaviyo, which keeps your members' Klaviyo profiles current for your email flows. Connect one with the settings and secrets it asks for, separately in Test and Live; secrets show only as set or not set, never their values. Anyone who can publish connects, edits or disconnects it; anyone else sees its status.
Some services connect through their own sign-in instead of secrets. Their panel has Connect with and the service's name: fill in the settings, grant Domino access on the service's page, and it sends you back to Integrations, which finishes connecting the service with the panel open. Only the person who started connecting can finish it, signed in to Console: a link to the service's page that you send someone else connects nothing. Domino keeps that access fresh on its own. If the service stops accepting it, for example because someone removed Domino in the service's settings, the card asks you to Reconnect, and nothing reaches the service until you do. Disconnect removes Domino's access at the service too. A card that says Not configured can't be connected yet: Domino hasn't set that service up.
What members see
- One link, then quests. A member who signed in with Discord is ready. Anyone else links their Discord or X account once from your app, and every quest can use it.
- Reactions and messages pay on their own. Reaction and message quests have no claim button. The member reacts or posts in Discord, and the points arrive.
- Roles show up in Discord. A role reward appears on the member's Discord profile, and your app can show whether it is still being granted.
- An outage is never held against them. If Discord or X is down, the check waits and tries again instead of saying no.
What you can do in Console
| Without code | Where |
|---|---|
| Connect, check, switch or disconnect your Discord server | Integrations |
| Connect a server another project of your organization already connected | Integrations, Or reuse |
| See which quests need Discord, whether events are arriving, and recent gaps | Integrations, on the Discord card |
| Choose the announcement channel and the cutoff date | The quest's Settings |
| Choose the role a quest hands out | The quest's Settings, Reward role |
| See role grants, retry a failed one, or keep a role your team owns | A member's Discord roles |
| Choose the channel a quest announces in | The quest's Settings |
| See a member's announcements, retry one that failed | A member's Announcements |
| Relink an X or Discord account a member already proved | A member's Identities |
The quests themselves come from code. Your developer starts from a ready-made recipe, called a snippet:
| Snippet | Rewards |
|---|---|
discord-membership | Members of your server, optionally only those with a role. |
discord-announcements | 🫡 reactions on announcements published after a date you choose. |
discord-contribution | Messages in a channel you choose, once per member per day. |
discord-moderator-recognition | The author of a message, when a moderator reacts with a heart. |
discord-role-reward | A Discord role, handed out when the member completes the quest. |
Connect Discord to a project
An organization administrator installs the bot, or sends a link to whoever manages the server, and manages the connection. Anyone else with read access can see its status. Once one project connects a server, anyone who can publish can connect it to another project of the same organization without installing the bot again.
Start the connection
Open Integrations in Console, select your project and the Test or Live environment, open the Discord card, and choose Connect Discord.
Install the bot
Discord asks you to choose a server you manage and install Domino's bot. You don't enter a server ID or bot token.
Return to Console
Come back to the same Console account and project to finish. Console shows the server's name when the connection is ready.
Send the install to your server admin
Adding a bot to a Discord server takes Manage Server (or Administrator) in that server. If you're a Console administrator without it, send a link to someone who has it. They don't need a Domino account.
- In Integrations, open the Discord card and choose Send to your server admin.
- Optionally enter the server's ID. The link then preselects that server and connects only it. Without an ID, it connects any server the person opening it manages.
- Choose Create link, copy it and send it to them.
- They open it and see which organization and project it connects to: your organization's name, the project's name, its slug and the environment. They choose Continue to Discord, add Domino's bot, and see a page naming the connected server and the project.
Refresh Integrations to see the server. The link connects the project and environment you made it in.
- Once, for seven days. A link connects one server, then stops working. Making a new link leaves earlier ones working.
- Only for the connection you had. If the project's Discord connection changes after you make the link (someone connects or disconnects a server), the link connects nothing. Make a new one.
- Anyone with the link can use it. Send it only to the person who manages the server. The project's audit records you, the link's creator, as who connected the server.
- It names your project. The project's slug is unique across Domino, so tell the person to expect it. Someone who gets a link naming a project they don't know shouldn't continue.
Manage the connection
- One server per environment. Each project has one Discord server in Test and one in Live, set up separately.
- Reconnect to switch servers. Reconnecting lets you pick another server.
- Check access. Check access checks the bot again. If the bot was removed or lost access, reconnect from this page.
- Outages are not rejections. A Discord outage makes the check try again later. It never fails the member's membership.
Use a server your organization already connected
A community with several projects installs the bot once. When another project of your organization already connects a server in the same environment, and the connection is ready, connect it here directly:
- Open Integrations in Console, in the project and environment you want to connect, and open the Discord card.
- Under Or reuse, choose Use beside the server.
Domino checks that its bot can still see the server, then connects it. Anyone who can publish in the project can do this, not just administrators. To add the bot to a server no project connects yet, an administrator uses Connect Discord. The project's audit records who connected the server and which project's connection it reused.
- Only a project without a server. A project that already has a Discord connection keeps it, even when it needs attention. An administrator reconnects, checks or disconnects it.
- Ready connections only. A server whose only connection needs attention isn't offered. Repair it in its project first.
Check that someone joined your server
Install the discord-membership snippet. It accepts members of your server who finished Discord's membership screening. Your developer can narrow it to a role, match names, or require a server boost.
To be checked, a member needs their Discord account in your app:
- Discord sign-in. A member who signs in with Discord is ready.
- Other logins. A member who signs in another way links Discord once with your app's account controls. Custom apps use the member API.
- Basic consent only. Discord asks for basic identity consent. Members don't authorize reading their server membership.
- Existing links. A Discord account a member linked before works too, as long as it's unambiguous.
Console labels these quests Quest code · Discord integration.
Recover a configuration failure
Connect Discord in Integrations, for the quest's project and environment, before members try a quest that needs it. A quest needs Discord when it checks membership, hands out a Discord role, announces in a channel, or lets you choose a Discord channel or role.
- Publishing still works without it. No way of publishing refuses a quest because Discord is missing or broken. The Discord card in Integrations always lists the quests that need Discord, and Quests shows a warning on each one until Discord is ready.
- Roles and channels are checked while connected. Once Discord is connected, a role or channel the server doesn't have still stops publication.
- Automatic quests catch up. An automatic quest published before Discord is ready, or whose connection breaks later, starts members' attempts at once, and they fail. When Discord becomes ready, by first connecting it or repairing it from needs attention, Domino starts a new attempt in the background for each member whose latest attempt failed this way. Current rules apply: a paused quest, or a member who no longer meets the prerequisites, gets no new attempt. Quests with a tracker trigger are the exception: their members keep the failed attempts, so retry those one by one.
When a check can't reach your server, the member's attempt usually stops with one of these reasons:
| Reason | What to do |
|---|---|
| The project hasn't connected its Discord server | Connect Discord in the same project's Test or Live environment. |
| The project's Discord connection needs attention | Use Check access, or reconnect the bot in Integrations. |
| Discord is not configured | Ask the platform administrator to set up the managed bot. |
Read the reason on the attempt in Console. For an attempt Domino doesn't start again, fix the connection first, then retry: a member trying again can't fix your project's setup. A retry checks Discord again. If the member's Discord account or your server changed since the attempt started, start a new submission instead, because an attempt keeps the account and server it first saw.
Automatic announcement reactions
Install the discord-announcements snippet to reward members who react 🫡 to announcements published after a date you choose. Choose the announcement channel and that date in Console, as described in Choose channels and cutoff dates. The server comes from the project's connection, and the bot must be able to see that channel.
How rewards are counted
- No claim button. The quest runs by itself when the member reacts.
- Once per announcement. Each member earns once per announcement. Removing a reaction doesn't take the reward back, and removing then re-adding it can't pay twice for the same message.
- The date is when the announcement was posted. The cutoff compares the announcement's publish time, not the time of the reaction.
- Hidden by default. The quest is hidden from members' quest lists unless your developer makes it visible.
Where Discord events come from
Domino runs one shared Discord bot that listens for new messages and reactions and hands each one to every project connected to that server. The Discord card in Integrations shows whether events are arriving. When the bot is blocked, Domino has to fix its configuration; it reconnects on its own afterwards, and a project can't restart it.
For how delivery, retries and its status endpoint work, see Discord Gateway delivery.
Coverage limits
Discord doesn't say when a reaction happened, and a reconnect can leave short gaps. So Domino can miss a reaction it has never seen before when Discord sends it again later, or one removed during a gap. The Discord card lists Recent coverage gaps. Treat Discord rewards as best effort, not a complete history of your server. The full rules are in Discord Gateway delivery.
Choose channels and cutoff dates
Open the announcement quest's Settings in Console:
| Setting | Behavior |
|---|---|
| Announcement channel | Lists the text and announcement channels of your connected server, grouped by category, with search. Channels the bot can't see are disabled with an explanation. |
| Announcements published after | A date and time in your local timezone. Domino stores the exact moment in UTC, so a teammate in another timezone sees the same moment in their own local time. |
Reconnect Discord from Integrations if the server connection is missing. Publish the settings to apply them.
- Empty settings pause the quest. A quest from code with an empty channel or role setting stays paused until you publish a choice.
- No silent substitutes. If a saved channel is deleted or the bot loses access, Domino doesn't pick another channel.
- Outages keep the old settings. If Discord is down while you save, the previous published settings stay in place.
Reward contributions and recognition
Reward channel messages
Install discord-contribution to reward messages in a channel you choose, or in its threads, at most once per member per day. Days follow the project calendar, which is UTC unless you choose a project timezone. Bot, webhook and Discord system messages never count. It checks that members took part, not how good their messages were, and it doesn't require consecutive days.
To require several days in a row, such as a 7-day streak, ask your developer to switch the snippet to a streak. Each accepted message fills the day it was posted, and the streak pays its milestone once.
Recognize moderator hearts
Install discord-moderator-recognition to reward the author of a message when a moderator reacts with a heart. Choose the contribution channel and the moderator role.
- Roles at the time. The moderator's roles are checked as they were when they reacted. Reacting to your own message doesn't count.
- Once per message. The author earns once per message, however many moderators react.
- Who needs an account. The moderator doesn't need to be a member. The author must have linked Discord already.
- No take-backs. Removing the reaction doesn't remove the reward.
Read a message from quest code
Quest code can also look up one message in your server, for example to check that a member posted it or replied to the right thread.
Grant a Discord role
A quest can hand out a Discord role as a reward, such as a Contributor role once a member finishes onboarding. Role rewards belong on quests; referral programs can't grant them directly.
Install the snippet
Install discord-role-reward and point its required quest, welcome, at your onboarding quest.
Choose the role
Choose Reward role in Console. The picker disables roles Domino can't hand out. Roles that an integration manages, such as a bot's own role, can't be rewards.
Give the bot permission
In Discord, give the bot Manage Roles and place its highest role above the reward role. Publication checks the bot's current permissions.
How delivery works
- Completion comes first. The role is promised in the same step as the member's other rewards. A Discord outage never undoes the completion or pays points twice.
- It keeps trying. Domino retries a failed grant with backoff. The member's details show whether a role is granting, retrying or granted. The Rewards page lists every role Domino has delivered, with all its deliveries counted, including roles no current quest grants.
- Bound to the original account. A role goes to the Discord account and server the member earned it with. Reconnecting another account can't receive it. Restoring the original account, server and permissions lets a pending grant go through.
- One role, many reasons. The same member can earn the same role from several quests, projects, or Test and Live. Revoking one of those completions doesn't remove a role another one still grants.
- Clean-up after revocation. When the last completion that granted a role is revoked, Domino removes the role, unless the member had it before Domino first granted it.
For the exact retry, timing and ownership rules, see Discord role delivery.
Recover or preserve a role
Open a member's Discord roles in Console. Once you've fixed the account, server or bot permissions, choose:
- Retry delivery for a grant that failed.
- Reconcile role for a removal that failed. Reconciling after a revocation never gives the role back.
A Discord permission error while Domino reads the member's roles stops that round of retries, just like a rejected grant. These actions need the review permission.
If an administrator or another bot takes over a role Domino granted, choose Preserve external assignment, give a reason and confirm. Domino then never removes that role automatically, in any project or environment that shares the member's Discord membership, until the role is gone and no accepted grant remains. Discord can't tell Domino about that takeover, so it's up to you to mark it. Remove an externally owned role in Discord yourself when appropriate.
Operator backends call management.retryRewardDelivery(id) and management.discordRoles.preserve(id, { reason }), and management.discordRoles.list() to list every role deliveries have granted, with the quests that requested it and its deliveries counted by status. CLI automation uses the same routes:
domino api GET /reward-deliveries/roles --json
domino api GET '/reward-deliveries?member=alice' --json
domino api POST /reward-deliveries/DELIVERY_ID/retry --json
printf '%s' '{"reason":"Administrator owns this assignment"}' | domino api POST /reward-deliveries/DELIVERY_ID/preserve - --jsonAnnounce in a channel
A quest can post a message in a channel of your server each time it pays, such as "@ana got +10 XP from @sam" when a moderator reacts to Ana's message. Telegram groups work the same way; see Announce tips in the group.
- Choose the channel. In the quest's Settings, choose the channel. The picker disables channels where the bot can't see or post.
- Give the bot permission. In Discord, give the bot View Channel and Send Messages there. Publication checks the bot's current permissions.
How it behaves:
- Points first. The message is posted after the rewards are saved. A Discord outage or a refused message never undoes them.
- It keeps trying. Domino retries a failed post with backoff for about a day. A post Discord refuses for good, for example because the bot lost access, shows why in the member's Announcements, and Retry delivery posts it once you've fixed the cause.
- Paced, never dropped. Domino posts at most one message a second per channel and waits out Discord's rate limits, so a burst waits in line instead of being lost.
- Only the people it names are pinged. Members are mentioned through their linked Discord account;
@everyone,@hereand role mentions never ping. A member without a linked Discord account is named by their Domino display name. A test-linked account, someone else's real account, is never mentioned. - Only your server. The channel must belong to the server connected for the project and environment. A Test environment never posts in a server any project connected to Live, and publication refuses one.
- Revoking. Revoking the completion cancels a message not yet posted; one already posted stays.
Disconnect or recover
Disconnecting the integration stops new Discord checks. Members keep their linked accounts, completions and earned rewards.
It doesn't remove the bot from your server, because another project or environment may use it. Remove the bot in Discord to uninstall it completely.
An attempt keeps the member's Discord account (or the fact that they had none) and the server from its first Discord check, and never checks them again. Changing either afterwards doesn't affect that attempt: use Start a new submission to check the new connection. Discord outages retry on their own. A missing connection, removed bot access or a connection that needs attention stop the attempt; see Recover a configuration failure.
Configure the managed Discord application
This is for the Domino platform administrator, not for project teams. Configure these as separate API Worker secrets from the same Discord application:
| Secret | Purpose |
|---|---|
DISCORD_CLIENT_ID | OAuth credential |
DISCORD_CLIENT_SECRET | OAuth credential |
DISCORD_BOT_TOKEN | Bot token |
- Deploy an API version that supports these secrets before setting them.
- Set both OAuth credentials before connecting.
Existing provider JSON doesn't need to be read or replaced. JSON-based Discord credentials keep working while neither dedicated OAuth credential is set. To read message text, see the callout in Read a message from quest code.
Verify X activity
Quests can check a member's X account: that they connected it, follow you, reposted a post, wrote a post, or changed their profile. Members connect X through your app's account linking. Your project needs no X setup; Domino supplies the access.
Check the connected account
The simplest X quest is "Connect your X account". It only checks that the member linked one.
Verify the action
This quest checks whether the member follows an X account you configure:
import { x, defineQuest, settings, manual, once, points, decision } from "@domino-sdk/relay/authoring";
import { community } from "./ledgers";
export default defineQuest({
key: "follow-on-x",
integrations: [x],
title: "Follow us on X",
settings: settings.object({
userId: settings.text({ label: "X user ID", default: "2244994945" }),
}),
trigger: manual(),
completion: once(),
rewards: [points(community, 50)],
async evaluate({ x, settings }) {
// null when the member has not linked an X account.
const follows = await x.follows({ userId: settings.userId });
if (follows === null) {
return decision.reject({ reason: "Connect your X account, then start a new submission." });
}
return follows
? decision.accept()
: decision.reject({ reason: "Follow the account, then start a new submission." });
},
});Declare the community points ledger in the same project. Use numeric X IDs as strings, not handles or URLs. Find an account's ID with domino x lookup HANDLE (details). Publish the quest through the normal quest workflow.
Handle retries and unavailable evidence
- Start a new submission after acting. An attempt keeps the X account the member had (or that they had none) at its first X check. A member who links another account, or follows you after a "no", starts a new submission so Domino reads X again.
- Outages don't count as "no". If X can't be reached, the check tries again later. It never becomes a "doesn't follow" or an accepted claim.
Start from a complete X quest
The SDK examples include five X quests:
Example file in packages/sdk/examples | Checks |
|---|---|
x-follow-quest.ts | The linked member follows the configured account ID. |
x-repost-quest.ts | The linked member reposted the configured post ID. |
x-bio-quest.ts | The linked profile bio contains the exact configured phrase. |
x-original-post-quest.ts | A submitted URL leads to the member's own post within the campaign dates, and the post passes the configured AI criteria. Unclear reviews go to staff. Only members of the approved-creators group (inGroup) can submit, because X doesn't allow apps that reward users for posting. |
x-profile-picture-quest.ts | The linked profile picture matches a deployed reference image, or AI confirms a customized version. See check images. |
Each example pays once per member and doesn't prove that a follow, repost or bio stays in place.
Restore a retained OAuth selection
A reviewer can open a member's Identities section and choose Relink for an X or Discord account the member already proved. Disconnect any other current account first. The change applies in that project and environment. Relinking keeps ownership, sign-in permissions and existing sessions, and records a fresh link time.