diff --git a/docs/activities/Development_Guides.mdx b/docs/activities/Development_Guides.mdx index dadec5c8a6..290ece5cb5 100644 --- a/docs/activities/Development_Guides.mdx +++ b/docs/activities/Development_Guides.mdx @@ -96,6 +96,13 @@ These guides include suggested development practices, SDK commands, and user flo +## Growth and Referrals + + + Encourage your users to share links to your activity by adding tracking and offering rewards for engagement. + + + ## Assets & Metadata @@ -812,7 +819,7 @@ Here's a basic example for retrieving a user's avatar and username ```javascript // We'll be referencing the user object returned from authenticate -const {user} = await DiscordRPC.commands.authenticate({ +const {user} = await discordSdk.commands.authenticate({ access_token: accessToken, }); @@ -839,13 +846,13 @@ Here's an example of how to retrieve the user's guild-specific avatar and nickna ```javascript // We'll be referencing the user object returned from authenticate -const {user} = await DiscordRPC.commands.authenticate({ +const {user} = await discordSdk.commands.authenticate({ access_token: accessToken, }); // When using the proxy, you may instead replace `https://discord.com` with `/discord` // or whatever url mapping you have chosen via the developer portal -fetch(`https://discord.com/api/users/@me/guilds/${DiscordRPC.guildId}/member`, { +fetch(`https://discord.com/api/users/@me/guilds/${discordSdk.guildId}/member`, { method: 'GET', headers: { Authorization: `Bearer ${accessToken}`, @@ -858,7 +865,7 @@ fetch(`https://discord.com/api/users/@me/guilds/${DiscordRPC.guildId}/member`, { let guildAvatarSrc = ''; // Retrieve the guild-specific avatar, and fallback to the user's avatar if (guildsMembersRead?.avatar) { - guildAvatarSrc = `https://cdn.discordapp.com/guilds/${DiscordRPC.guildId}/users/${user.id}/avatars/${guildsMembersRead.avatar}.png?size=256`; + guildAvatarSrc = `https://cdn.discordapp.com/guilds/${discordSdk.guildId}/users/${user.id}/avatars/${guildsMembersRead.avatar}.png?size=256`; } else if (user.avatar) { guildAvatarSrc = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`; } else { @@ -879,6 +886,88 @@ This example is being done entirely on the client, however, a more common patter --- +### Prompting Users to Share Incentivized Links + +Incentivized sharing can help grow your Activity through network effects. This guide covers implementing a reward system for users who share links and those who click them. + +#### Implementation Overview +1. Create and track an incentivized link for a promotional campaign, then prompt users to share the link +2. Handle incoming referrals and grant valid rewards + +#### Sharing Links + +When implementing sharing, you'll need to: +1. Generate a unique ID for tracking the promotion +2. Call the [`shareLink`](#DOCS_DEVELOPER_TOOLS_EMBEDDED_APP_SDK/sharelink) command +3. Track the share attempt + +```javascript +// Generate a unique ID for this promotion +// This could be per-campaign, per-user, or per-share depending on your needs +const customId = await createPromotionalCustomId(); + +try { + const { success } = await discordSdk.commands.shareLink({ + message: 'Click this link to redeem 5 free coins!', + custom_id: customId, + // referrer_id is optional - if omitted, the current user's ID is used + }); + + if (success) { + // Track successful share for analytics/limiting + await trackSuccessfulShare(customId); + } +} catch (error) { + // Handle share failures appropriately + console.error('Failed to share link:', error); +} +``` + +#### Handling Incoming Referrals +When a user clicks a shared link, your activity will launch with referral data available through the SDK: + +```javascript +// Early in your activity's initialization +async function handleReferral() { + // Validate the referral data + if (!discordSdk.customId || !discordSdk.referrerId) { + return; + } + + try { + // Verify this is a valid promotion and hasn't expired + const promotion = await validatePromotion(discordSdk.customId); + if (!promotion) { + console.log('Invalid or expired promotion'); + return; + } + + // Prevent self-referrals + if (discordSdk.referrerId === currentUserId) { + console.log('Self-referrals not allowed'); + return; + } + + // Grant rewards to both users + await grantRewards({ + promotionId: discordSdk.customId, + referrerId: discordSdk.referrerId, + newUserId: currentUserId + }); + } catch (error) { + console.error('Failed to process referral:', error); + } +} +``` + +#### Link Sharing Best Practices +- Generate unique, non-guessable `customId`s +- Track and validate referrals to prevent abuse +- Handle edge cases like expired promotions gracefully +- Consider implementing cool-down periods between shares + +--- + ### Preventing unwanted activity sessions Activities are surfaced through iframes in the Discord app. The activity website itself is publicly reachable at `.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.