> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snagsolutions.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing social accounts

> This recipe explains how to use Snag social integration API to let your users connect their social accounts to their Snag profile.

<head>
  <script type="application/ld+json">
    {JSON.stringify({
            "@context": "https://schema.org",
            "@graph": [
              {
                "@type": "TechArticle",
                "headline": "Managing social accounts",
                "description": "This recipe explains how to use Snag social integration API to let your users connect their social accounts to their Snag profile.",
                "author": {"@type": "Organization", "name": "Snag Solutions", "url": "https://www.snagsolutions.io/"},
                "publisher": {"@type": "Organization", "name": "Snag Solutions", "url": "https://www.snagsolutions.io/", "logo": {"@type": "ImageObject", "url": "https://assets.snagsolutions.io/public/docs/snag-logo-dark-no-bg.svg"}},
                "mainEntityOfPage": "https://docs.snagsolutions.io/loyalty/managing-social-accounts"
              },
              {
                "@type": "BreadcrumbList",
                "itemListElement": [
                  {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://docs.snagsolutions.io/welcome"},
                  {"@type": "ListItem", "position": 2, "name": "Loyalty", "item": "https://docs.snagsolutions.io/loyalty/loyalty-overview"},
                  {"@type": "ListItem", "position": 3, "name": "Advanced workflows", "item": "https://docs.snagsolutions.io/loyalty/managing-user-accounts"},
                  {"@type": "ListItem", "position": 4, "name": "Managing social accounts"}
                ]
              }
            ]
          })}
  </script>
</head>

## Overview

This guide explains how to use Snag's social integration API to let your users connect their social accounts to their Snag profile. After creating a user in Snag (as explained in [Managing User Accounts](/loyalty/managing-user-accounts)), you can use the social authentication endpoints to connect various social platforms to that user's profile.

<Warning>
  You must first create a user using the [Managing User Accounts](/loyalty/managing-user-accounts) guide to obtain a `userId` before connecting social accounts. The `userId` parameter required for all social account connections comes from the user creation endpoint response.
</Warning>

## Supported Social Platforms

Snag supports connecting the following social platforms:

* Twitter
* Discord
* Telegram
* Epic Games
* Steam
* Google
* Tiktok
* Email
* Reddit
* Phone

## Connecting a Social Account

### Endpoint

```web/apps/docs/loyalty/managing-social-accounts.mdx theme={null}
GET /api/{authType}/auth
```

Where `{authType}` is one of: `twitter`, `discord`, `telegram`, `epic`, `steam` , `google` , `Reddit` or `tiktok`.

### Query Parameters

| Parameter    | Type   | Required | Description                                                                                                                                     |
| ------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| userId       | UUID   | Yes      | The ID of the user received from the [user creation endpoint](/loyalty/managing-user-accounts). This must be obtained by creating a user first. |
| websiteId    | UUID   | No       | The ID of the website (if applicable)                                                                                                           |
| redirect     | String | Yes      | The URL where the user will be redirected after completing authentication                                                                       |
| responseType | String | No       | The type of response to return (`redirect` or `json`). Defaults to `redirect`                                                                   |

<Note>
  **Security Note regarding Redirect URIs**

  To simplify integration, we allow unvalidated `redirect` URIs when using the API. This means you can set the redirect URL to any destination.

  Since this endpoint is protected by your API key, only authorized requests can initiate the flow with a custom redirect. However, because the redirect URL can be set to anything, it is **your responsibility** to ensure that you do not include sensitive data in the URL parameters.
</Note>

### Example Request

```
GET /api/twitter/auth?userId=123e4567-e89b-12d3-a456-426614174001&redirect=https://your-app.com/auth-callback
```

### Authentication Flow

1. Call the `/api/{authType}/auth` endpoint with the required parameters
2. The API will return a URL that you should redirect your user to
3. The user will authenticate with the social platform and grant permissions
4. After successful authentication, the user will be redirected to the URL specified in the `redirect` parameter
5. The social account is now connected to the user's Snag profile

## Connecting Email

Use this flow to associate and verify a user's email with their Snag profile.

### Endpoint

```
GET /api/email/auth
```

### Query Parameters

| Parameter     | Type   | Required    | Description                                                                                            |
| ------------- | ------ | ----------- | ------------------------------------------------------------------------------------------------------ |
| emailAddress  | String | Yes         | The user's email address to verify.                                                                    |
| redirect      | String | No          | URL to redirect the user to after verification. We append `status` and, on failure, optional `error`.  |
| userId        | UUID   | Conditional | Required when calling server-to-server with an API key to target a specific user.                      |
| walletAddress | String | Conditional | Alternative to `userId` when calling server-to-server with an API key; creates/links a user by wallet. |
| websiteId     | UUID   | No          | Your website identifier if applicable.                                                                 |

### Headers

* `x-api-key`: Required.

### Flow

<Steps>
  <Step title="Initiate email verification">
    Call the endpoint with `emailAddress` (and optional `redirect`). This stores the pending email on the user's metadata and sends a verification email containing a secure link.

    <RequestExample>
      ```bash cURL theme={null}
      curl -X GET 'https://admin.snagsolutions.io/api/email/auth' \
        -H 'x-api-key: YOUR_SNAG_API_KEY' \
        -G \
        --data-urlencode 'emailAddress=user@example.com' \
        --data-urlencode 'userId=123e4567-e89b-12d3-a456-426614174001' \
        --data-urlencode 'redirect=https://your-app.com/email-verified'
      ```
    </RequestExample>

    <ResponseExample>
      ```json Success theme={null}
      { "message": "Verification email sent.", "userId": "123e4567-e89b-12d3-a456-426614174001" }
      ```
    </ResponseExample>
  </Step>

  <Step title="User clicks verification link">
    The email contains a link to `GET /api/email/auth/connect?token=...`. Snag validates the token and verifies the email.
  </Step>

  <Step title="Handle the final redirect">
    After verification, the user is redirected to your `redirect` URL (or a default verification page) with a status:

    * `status=SUCCESS` on success
    * `status=EXPIRED` if the link expired
    * `status=INVALID` and `error=INVALID_CODE` for invalid tokens

    Example: `https://your-app.com/email-verified?status=SUCCESS`
  </Step>
</Steps>

### Handling Email Account Conflicts

If the email is already verified for another user on the same website, the user is redirected to your `redirect` with `error=MAXIMUM_ACCOUNT_LINKED`. Handle this scenario the same way as described in [Handling Social Account Conflicts](#handling-social-account-conflicts).

## Connecting Phone

Use this flow to associate and verify a user's phone number with their Snag profile. Phone verification uses Twilio Verify to send an OTP code via SMS.

<Note>
  Phone numbers must be in [E.164 format](https://www.twilio.com/docs/glossary/what-e164) (e.g., `+14155552671`). This is the international phone number format required by Twilio.
</Note>

### Step 1: Send Verification Code

#### Endpoint

```
POST /api/phone/auth
```

#### Headers

* `x-api-key`: Required.
* `Content-Type`: `application/json`

#### Body Parameters

| Parameter     | Type   | Required    | Description                                                                       |
| ------------- | ------ | ----------- | --------------------------------------------------------------------------------- |
| phoneNumber   | String | Yes         | The phone number to verify in E.164 format (e.g., `+14155552671`).                |
| userId        | UUID   | Conditional | Required when calling server-to-server with an API key to target a specific user. |
| walletAddress | String | Conditional | Alternative to `userId` when calling server-to-server with an API key.            |
| websiteId     | UUID   | No          | Your website identifier if applicable.                                            |

### Step 2: Verify OTP Code

#### Endpoint

```
POST /api/phone/auth/connect
```

#### Headers

* `x-api-key`: Required.
* `Content-Type`: `application/json`

#### Body Parameters

| Parameter     | Type   | Required    | Description                                                                       |
| ------------- | ------ | ----------- | --------------------------------------------------------------------------------- |
| phoneNumber   | String | Yes         | The phone number being verified, in E.164 format.                                 |
| code          | String | Yes         | The OTP code received via SMS.                                                    |
| userId        | UUID   | Conditional | Required when calling server-to-server with an API key to target a specific user. |
| walletAddress | String | Conditional | Alternative to `userId` when calling server-to-server with an API key.            |
| websiteId     | UUID   | No          | Your website identifier if applicable.                                            |

### Flow

<Steps>
  <Step title="Send verification code">
    Call `POST /api/phone/auth` with the phone number. Snag sends an OTP code to the user via SMS using Twilio Verify.

    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST 'https://admin.snagsolutions.io/api/phone/auth' \
        -H 'x-api-key: YOUR_SNAG_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "phoneNumber": "+14155552671",
          "userId": "123e4567-e89b-12d3-a456-426614174001"
        }'
      ```
    </RequestExample>

    <ResponseExample>
      ```json Success theme={null}
      {
        "message": "Verification code sent.",
        "userId": "123e4567-e89b-12d3-a456-426614174001"
      }
      ```
    </ResponseExample>
  </Step>

  <Step title="User receives SMS code">
    The user receives a 6-digit OTP code via SMS on their phone.
  </Step>

  <Step title="Verify the code">
    Call `POST /api/phone/auth/connect` with the phone number and the OTP code. Snag verifies the code with Twilio and connects the phone number to the user's profile.

    <RequestExample>
      ```bash cURL theme={null}
      curl -X POST 'https://admin.snagsolutions.io/api/phone/auth/connect' \
        -H 'x-api-key: YOUR_SNAG_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "phoneNumber": "+14155552671",
          "code": "123456",
          "userId": "123e4567-e89b-12d3-a456-426614174001"
        }'
      ```
    </RequestExample>

    <ResponseExample>
      ```json Success theme={null}
      {
        "success": true,
        "message": "Phone number verified."
      }
      ```
    </ResponseExample>
  </Step>
</Steps>

### Disconnecting Phone

To disconnect a phone number from a user's profile:

```
POST /api/phone/auth/disconnect
```

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST 'https://admin.snagsolutions.io/api/phone/auth/disconnect' \
    -H 'x-api-key: YOUR_SNAG_API_KEY' \
    -H 'Content-Type: application/json' \
    -d '{
      "userId": "123e4567-e89b-12d3-a456-426614174001"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json Success theme={null}
  {
    "message": "Phone disconnected."
  }
  ```
</ResponseExample>

### Handling Phone Account Conflicts

If the phone number is already verified for another user on the same website, the verify endpoint returns `error: 'MAXIMUM_ACCOUNT_LINKED'` in the response body:

```json theme={null}
{
  "success": false,
  "error": "MAXIMUM_ACCOUNT_LINKED",
  "message": "This phone number is already linked to another account."
}
```

Handle this scenario the same way as described in [Handling Social Account Conflicts](#handling-social-account-conflicts).

### Handling Social Account Conflicts

If the social account is already associated with a different user, the redirect URL will include two query parameters:

1. `error = 'MAXIMUM_ACCOUNT_LINKED'` - Indicates that the account is already linked to another user
2. `accountLinkData` - A JWT verification token that contains the necessary information to process the account transfer

In this case, you should:

1. Display a confirmation prompt to the user asking if they want to disconnect the account from the other user and link it to their current profile
2. If the user confirms, make a POST request to `/api/users/verify` with the following payload:

```json theme={null}
{
  "accountLinkData": "jwt_verification_token",
  "userId": "current_user_id"
}
```

This will:

* Disconnect the social account from the previous user
* Connect it to the current user's profile

### Google OAuth YouTube Channel Errors

When using Google OAuth, if the user's YouTube channel does not exist, the API will return an error with `error = 'YOUTUBE_CHANNEL_NOT_FOUND'`. This typically happens when:

* The user has never created a YouTube channel
* The YouTube channel was deleted or suspended
* The user's Google account doesn't have YouTube access

To handle this error, you should:

1. Display a user-friendly message explaining that a YouTube channel is required
2. Prompt the user to create a YouTube channel first
3. Provide instructions on how to create a YouTube channel
4. Allow the user to retry the Google OAuth flow after creating their channel

<Note>
  YouTube channels are automatically created when users upload their first video
  or customize their channel. Users can also manually create a channel by
  visiting [youtube.com](https://youtube.com) and following the setup prompts.
</Note>

### Google OAuth Requirements

When using Google OAuth integration, whether with Snag's default OAuth application or your own custom OAuth application, you need to ensure the following permissions (scopes) are configured:

* `https://www.googleapis.com/auth/youtube.readonly` - Required to read YouTube channel information
* `https://www.googleapis.com/auth/userinfo.email` - Required to access user email address
* `https://www.googleapis.com/auth/userinfo.profile` - Required to access user profile information

<Warning>
  Google requires verification of OAuth applications that request sensitive scopes. If you're using your own custom OAuth application, you must complete the verification process in the Google Cloud Console before the OAuth flow will work properly. This verification process can take several days to complete.

  Contact the Snag team if you need assistance with the Google OAuth verification process or if you're experiencing issues with Google authentication.
</Warning>

### Example Implementation with Conflict Handling

```javascript theme={null}
// Server-side implementation (e.g., in Node.js/Express)
app.get('/auth-callback', async (req, res) => {
  const { error, accountLinkData } = req.query
  const userId = req.session.userId // Get from authenticated session

  if (error === 'MAXIMUM_ACCOUNT_LINKED') {
    // Render a confirmation page or return JSON response
    return res.json({
      requiresConfirmation: true,
      accountLinkData,
    })
  }

  if (error === 'YOUTUBE_CHANNEL_NOT_FOUND') {
    // Handle YouTube channel not found error
    return res.json({
      error: 'YOUTUBE_CHANNEL_NOT_FOUND',
      message:
        'YouTube channel not found. Please create a YouTube channel first and then re-authenticate via Google OAuth.',
      requiresChannelCreation: true,
    })
  }

  // Handle successful connection
  res.redirect('/profile')
})

// Handle confirmation
app.post('/confirm-account-transfer', async (req, res) => {
  const { accountLinkData, userId } = req.body

  try {
    const response = await fetch(
      'https://admin.snagsolutions.io/api/users/verify',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'x-api-key': `${process.env.SNAG_API_KEY}`, // API key stored securely on server
        },
        body: JSON.stringify({
          accountLinkData,
          userId,
        }),
      }
    )

    if (response.ok) {
      res.redirect('/profile')
    } else {
      throw new Error('Failed to transfer account')
    }
  } catch (error) {
    console.error('Error transferring account:', error)
    res.status(500).send('Error transferring account')
  }
})
```

### Example Tiktok Implementation

```javascript theme={null}
// Server-side implementation (e.g., in Node.js/Express)
app.get('/connect-tiktok', async (req, res) => {
  const userId = req.query.userId // Get from authenticated session
  const redirectUrl = 'https://example.com/tiktok-redirect'

  try {
    // Step 1: Get state (JWT) from your server
    // This initiates the TikTok OAuth flow and returns a JWT containing the verification code
    const response = await fetch(
      `https://admin.snagsolutions.io/api/tiktok/auth?userId=${userId}&redirect${redirect_uri}`,
      {
        headers: {
          'x-api-key': `${process.env.SNAG_API_KEY}`, // API key stored securely on server
        },
      }
    )

    const data = await response.json()

    // The response contains a `state` (JWT).
    // Decode the JWT to extract the 8-digit code that the user must add to their TikTok bio.
    res.json(data)
  } catch (error) {
    console.error('Error initiating TikTok connection:', error)
    res.status(500).send('Error initiating TikTok connection')
  }
})

app.get('/tiktok-redirect', async (req, res) => {
  const { error, state, code } = req.query

  if (error === 'INVALID_TIKTOK_CODE') {
    // Step 1: Handle TikTok verification failure
    // - show error to user
    // - Log the failure for debugging
    // - Optionally redirect to error page or show error message
  }

  // Step 2: If no error,
  // - Call user endpoint
  // - Verify the TikTok connection was successful (tiktokUser,tiktokUserId,tiktokVerifiedAt)
})

// Client-side code to initiate the flow
function decodeJwtForCode(stateJwt) {
  if (!stateJwt || typeof stateJwt !== 'string') {
    throw new Error('Invalid state JWT')
  }

  const decoded = jwt.decode(stateJwt)
  if (decoded && typeof decoded === 'object' && 'generatedCode' in decoded) {
    return decoded.generatedCode
  }

  throw new Error('generatedCode not found in JWT')
}

async function connectTiktokAccount(userId, tiktokProfileUrl) {
  // Step 1: Get state (JWT) from your server
  const response = await fetch(`/connect-tiktok?userId=${userId}`)
  const { state } = await response.json()

  // Decode JWT → extract the 8-digit code
  const code = decodeJwtForCode(state)

  // Show popup asking user to add code to TikTok bio
  if (
    window.confirm(
      `Please add this code to your TikTok bio: ${code}\n\nClick OK once you have updated your bio.`
    )
  ) {
    // Step 2: Redirect user to TikTok connect with state and tiktokProfileUrl
    // After verification, they'll be redirected back to your redirect URL
    const connectUrl = `https://admin.snagsolutions.io/api/tiktok/auth/connect?state=${state}&tiktokProfileUrl=${encodeURIComponent(
      tiktokProfileUrl
    )}`

    window.location.href = connectUrl
  } else {
    console.log('User cancelled TikTok bio update')
  }
}
```

### Example Reddit Implementation

<Warning>
  Reddit integration only supports JSON response type. The `redirect` parameter
  and `responseType=redirect` are not supported for Reddit authentication.
</Warning>

```javascript theme={null}
// Server-side implementation (e.g., in Node.js/Express)
app.get('/connect-reddit', async (req, res) => {
  const userId = req.query.userId // Get from authenticated session

  try {
    // Step 1: Get state (JWT) from your server
    // This initiates the Reddit verification flow and returns a JWT containing the verification code
    const response = await fetch(
      `https://admin.snagsolutions.io/api/reddit/auth?userId=${userId}&responseType=json`,
      {
        headers: {
          'x-api-key': `${process.env.SNAG_API_KEY}`, // API key stored securely on server
        },
      }
    )

    const data = await response.json()

    // The response contains a `state` (JWT).
    // Decode the JWT to extract the 8-digit code that the user must add to their Reddit bio.
    res.json(data)
  } catch (error) {
    console.error('Error initiating Reddit connection:', error)
    res.status(500).send('Error initiating Reddit connection')
  }
})

// Client-side code to initiate the flow
function decodeJwtForCode(stateJwt) {
  if (!stateJwt || typeof stateJwt !== 'string') {
    throw new Error('Invalid state JWT')
  }

  const decoded = jwt.decode(stateJwt)
  if (decoded && typeof decoded === 'object' && 'generatedCode' in decoded) {
    return decoded.generatedCode
  }

  throw new Error('generatedCode not found in JWT')
}

async function connectRedditAccount(userId, redditProfileUrl) {
  // Step 1: Get state (JWT) from your server
  const response = await fetch(`/connect-reddit?userId=${userId}`)
  const { state } = await response.json()

  // Decode JWT → extract the 8-digit code
  const code = decodeJwtForCode(state)

  // Show popup asking user to add code to Reddit bio
  if (
    window.confirm(
      `Please add this code to your Reddit bio: ${code}\n\nClick OK once you have updated your bio.`
    )
  ) {
    // Step 2: Call Reddit connect API with state and redditProfileUrl
    const connectUrl = `https://admin.snagsolutions.io/api/reddit/auth/connect?state=${state}&redditProfileUrl=${encodeURIComponent(
      redditProfileUrl
    )}`

    const connectResponse = await fetch(connectUrl)
    const result = await connectResponse.json()

    if (result.success) {
      alert('Reddit account connected successfully!')
    } else {
      alert(result.message || 'Failed to connect Reddit account')
    }
  } else {
    console.log('User cancelled Reddit bio update')
  }
}
```

### Example Implementation

```javascript theme={null}
// Server-side implementation (e.g., in Node.js/Express)
app.get('/connect-twitter', async (req, res) => {
  const userId = req.query.userId // Get from authenticated session

  try {
    const response = await fetch(
      `https://admin.snagsolutions.io/api/twitter/auth?userId=${userId}&redirect=https://your-app.com/auth-callback`,
      {
        headers: {
          'x-api-key': `${process.env.SNAG_API_KEY}`, // API key stored securely on server
        },
      }
    )

    const data = await response.json()

    // Redirect the user to the authentication URL
    res.redirect(data.url)
  } catch (error) {
    console.error('Error connecting Twitter account:', error)
    res.status(500).send('Error connecting Twitter account')
  }
})

// Client-side code to initiate the flow
function connectTwitterAccount(userId) {
  // Redirect to your server endpoint
  window.location.href = `/connect-twitter?userId=${userId}`
}
```

## Response Handling

### Success Response (200 OK)

```json theme={null}
{
  "url": "https://auth-provider.com/oauth/authorize?client_id=xxx&redirect_uri=xxx&state=xxx"
}
```

The `url` property contains the authentication URL that you should redirect your user to.

## Custom OAuth Applications

Snag allows you to use your own OAuth applications for **Twitter**, **Discord**, **Epic Games**, **Google**, and **Steam** integrations. This enables you to maintain your brand identity throughout the authentication flow and have more control over the user experience.

To configure your own OAuth credentials, navigate to the **Integrations** tab in your admin dashboard and enter your client ID and client secret for the desired platform. The changes take effect immediately for all new authentication requests.

You will also need to add the following redirect URLs to your OAuth application:

* `https://snag-render.com/api/twitter/auth/callback`
* `https://snag-render.com/api/discord/auth/callback`
* `https://snag-render.com/api/epic/auth/callback`
* `https://snag-render.com/api/steam/auth/callback`
* `https://snag-render.com/api/google/auth/callback`

### Google OAuth Application Setup

When configuring a custom Google OAuth application, ensure your OAuth consent screen includes the following required scopes:

* `https://www.googleapis.com/auth/youtube.readonly`
* `https://www.googleapis.com/auth/userinfo.email`
* `https://www.googleapis.com/auth/userinfo.profile`

<Warning>
  Google requires verification of OAuth applications requesting sensitive scopes. You must complete the verification process in the Google Cloud Console before your custom OAuth application will work. The verification process typically takes several days and may require additional documentation about your application's use of these scopes.
</Warning>

## Troubleshooting

### 403 Forbidden Response

If you receive a `403 Forbidden` response when attempting to connect a social account, check the following:

<AccordionGroup>
  <Accordion title="Missing or invalid API key">
    Ensure you're including the `x-api-key` header in your request with a valid API key. The API key must be associated with your Snag account and have the necessary permissions.

    ```javascript theme={null}
    headers: {
      'x-api-key': 'YOUR_SNAG_API_KEY'
    }
    ```
  </Accordion>

  <Accordion title="Invalid userId">
    Verify that the `userId` you're using:

    * Was obtained from the [user creation endpoint](/loyalty/managing-user-accounts)
    * Uses the `userId` field from the response, **not** the `id` field
    * Is a valid UUID format
    * Belongs to a user that exists in your Snag account
    * Is associated with the same website/account as your API key

    <Warning>
      Make sure to use `userId` from the user creation response, not `id`. Using `id` instead of `userId` will result in a 403 Forbidden error.
    </Warning>

    If you haven't created the user yet, follow the [Managing User Accounts](/loyalty/managing-user-accounts) guide first.
  </Accordion>

  <Accordion title="Insufficient permissions">
    Your API key may not have the required permissions to connect social accounts. Contact the Snag team to verify your API key has the necessary scopes enabled.
  </Accordion>

  <Accordion title="Website ID mismatch">
    If you're using a `websiteId` parameter, ensure it matches the website associated with your API key and the user's account. Mismatched website IDs can result in 403 errors.
  </Accordion>
</AccordionGroup>

## Next Steps

After connecting social accounts, you can use this information to enhance the user experience in your application and leverage it for loyalty program features.
