Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom incentivized links #7438

Merged
merged 7 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/activities/building-an-activity.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ npm install
npm run dev
```

If you visit http://localhost:5173/ you should see a vanilla JS frontend template running with [Vite](https://vitejs.dev/).
If you visit http://localhost:3000/ you should see a vanilla JS frontend template running with [Vite](https://vitejs.dev/).

While it's not much at the moment, in the following steps we'll connect it to the backend services, make it runnable in Discord, and power it up by populating it with data we pull from Discord APIs.

Expand Down Expand Up @@ -162,11 +162,11 @@ cp example.env .env
> **Secure Your Secrets**<br /> Your `DISCORD_CLIENT_SECRET` and `DISCORD_BOT_TOKEN` are *highly* sensitive secrets. Never share either secrets or check them into any kind of version control.

Back in your app's settings, click on **OAuth2** on the sidebar:
1. **Client ID**: Copy the value for Client ID and add it to your `.env` file as **`VITE_DISCORD_CLIENT_ID`**. This is the public ID that Discord associates with your app, and is almost always the same as your App ID.
1. **Client ID**: Copy the value for Client ID and add it to your `.env` file as **`VITE_CLIENT_ID`**. This is the public ID that Discord associates with your app, and is almost always the same as your App ID.
2. **Client Secret**: Copy the value for Client Secret and add it to your `.env` as **`DISCORD_CLIENT_SECRET`**. This is a private, sensitive identifier that your app will use to grant an OAuth2 `access_token`, and should never be shared or checked into version control.

> info
> **Why is there a VITE_ prefix before our Client ID?**<br />Prefixing the `DISCORD_CLIENT_ID` environment variable with `VITE_` makes it accessible to our client-side code. This security measure ensures that only the variables you intend to be accessible in the browser are available, and all other environment variables remain private. You can read details in the [Vite documentation](https://vitejs.dev/guide/env-and-mode).
> **Why is there a VITE_ prefix before our Client ID?**<br />Prefixing the `CLIENT_ID` environment variable with `VITE_` makes it accessible to our client-side code. This security measure ensures that only the variables you intend to be accessible in the browser are available, and all other environment variables remain private. You can read details in the [Vite documentation](https://vitejs.dev/guide/env-and-mode).

<Collapsible title="Step 2 Checkpoint" icon="list" open>

Expand Down Expand Up @@ -265,7 +265,7 @@ Your app should start and you should see output similar to the following:
```
VITE v5.0.12 ready in 100 ms

➜ Local: http://localhost:5173/
➜ Local: http://localhost:3000/
➜ Network: use --host to expose
➜ press h + enter to show help
```
Expand All @@ -276,10 +276,10 @@ We'll use the Local URL as our publicly-accessible URL in the next step.

Next, we'll need to set up the public endpoint that serves the Activity's frontend. To do that, we'll create a tunnel with a reverse proxy. While we'll be using [`cloudflared`](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) in this guide, you can use [ngrok](https://ngrok.com/docs) or another reverse proxy solution if you prefer.

While your app is still running, open another terminal window and start a network tunnel that listens to the port from the last step (in this case, port `5173`):
While your app is still running, open another terminal window and start a network tunnel that listens to the port from the last step (in this case, port `3000`):

```
cloudflared tunnel --url http://localhost:5173
cloudflared tunnel --url http://localhost:3000
```

When you run `cloudflared`, the tunnel will generate a public URL and you'll see output similar to the following:
Expand Down Expand Up @@ -680,7 +680,7 @@ At this point, you should have your Activity up and running. For Step 7, you sho

Congrats on building your first Activity! 🎉

This is an intentionally simple example to get you started with the communication between your Activity and Discord using the Embedded App SDK and APIs. From here, you can explore the [Activities documentation](#DOCS_ACTIVITIES_OVERVIEW) and other resources.
This is an intentionally simple example to get you started with the communication between your Activity and Discord using the Embedded App SDK and APIs. From here, you can explore the [Activities documentation](#DOCS_ACTIVITIES_OVERVIEW) and other resources.

<Container>
<Card title="Development Guides" link="#DOCS_DEVELOPER_TOOLS_COMMUNITY_RESOURCES" icon="WrenchIcon">
Expand Down
92 changes: 92 additions & 0 deletions docs/activities/development-guides.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ These guides include suggested development practices, SDK commands, and user flo
<Card title="Prompting Users to Share Incentivized Links" link="#DOCS_ACTIVITIES_DEVELOPMENT_GUIDES/prompting-users-to-share-incentivized-links">
Encourage your users to share links to your activity by adding tracking and offering rewards for engagement.
</Card>

<Card title="Creating and Managing Custom Incentivized Links" link="#DOCS_ACTIVITIES_DEVELOPMENT_GUIDES/creating-and-managing-custom-incentivized-links">
For off-platform sharing of rewards, promotions, or limited time experiences.
</Card>

<Card title="Generating a Custom Link Within Your Activity" link="#DOCS_ACTIVITIES_DEVELOPMENT_GUIDES/generating-a-custom-link-within-your-activity">
An API to be able to generate ephemeral links with a customizable embed.
</Card>
</Container>

## Assets & Metadata
Expand Down Expand Up @@ -980,6 +988,90 @@ async function handleReferral() {

---

### Creating and Managing Custom Incentivized Links

This guide covers creating a customizable [Incentivized Link](#DOCS_ACTIVITIES_DEVELOPMENT_GUIDES/prompting-users-to-share-incentivized-links) through the dev portal, and then retrieving the link to be able to share it off-platform. Incentivized Links are used to customize how the embed appears to users.

#### Creating a Link

1. In your Application's portal, visit the Custom Links page under the Activities heading in the navigation pane.
2. On the Custom Links page, click `Create New` to create a new link.
3. You will need to upload an image with an aspect ratio of 43:24.
4. Title, and description are also required.
5. `custom_id` is an optional field, an explicit `custom_id` query parameter on the link itself will always override the set `custom_id`.
6. Click Save.

#### Editing a Link

1. Click on a row to open up the modal with all of the data loaded in ready for your edits.
2. Change the description to something else.
3. Click Update.

#### Copying a Link

Once you're satisfied with your changes you can click on the copy icon on the row, it'll change colors to green indicating that it copied to your clipboard. You are now able to share this link anywhere. The link will look like: `https://discord.com/activities/<your Activity ID>?link_id=0-123456789`. Even if you've set a `custom_id`, it won't be explicitly included in the link but will be loaded once a user clicks on the link. You can then further shorten this URL if you'd like.

#### Deleting a Link

1. Click on the trash icon on the row of the link you're trying to delete.
2. You'll have a confirm dialog pop up.

> warn
> Deleting is irreversible and immediate. Ensure that your link isn't in active use before deleting and/or that your activity gracefully handles any click-throughs from the link.

#### Best Practices

- Generate unique, non-guessable `customId`s
- Track and validate referrals to prevent abuse
- Gracefully handle expirations in your activity for any custom links that are limited time but still live off-platform.

#### User Experience

![custom-link-embed](activities/custom-link-embed.png)

Users will see an embed with your information displayed. Clicking "Play" opens the activity and passes through the `custom_id` you've set. A `referrer_id` will be present for links shared on Discord.

---

### Generating a Custom Link Within Your Activity

This guide covers creating a customizable [Incentivized Link](#DOCS_ACTIVITIES_DEVELOPMENT_GUIDES/prompting-users-to-share-incentivized-links) within your activity, and using the `shareLink` API to share the link.

* Allows you to customize the way the link is presented to users via the embed
* Can be generated on-demand within your activity
* Ephemeral, 30 day TTL
* Does not show up in the developer portal

#### Generating a Link

```
// Convert an image array buffer to base64 string
const image = base64EncodedImage;

// Generate the quick activity link
const linkIdResponse = await fetch(`${env.discordAPI}/applications/${env.applicationId}/quick-links/`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
},
body: {
custom_id: 'user_123/game_456'
description: 'I just beat level 10 with a perfect score',
title: 'Check out my high score!',
image,
}
});
const {link_id} = await linkIdResponse.json();

// Open the Share modal with the generated link
const {success} = await discordSdk.commands.shareLink({
linkId: link_id
});
success ? console.log('User shared link!') : console.log('User did not share link!');
```

---

### Preventing unwanted activity sessions

Activities are surfaced through iframes in the Discord app. The activity website itself is publicly reachable at `<application_id>.discordsays.com`. Activities will expect to be able to communicate with Discord's web or mobile client via the Discord SDK's RPC protocol. If a user loads the activity's website in a normal browser, the Discord RPC server will not be present, and the activity will likely fail in some way.
Expand Down
Binary file added images/activities/custom-link-embed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.