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

# Authentication

## Authentication Methods

Loop provides two authentication methods for accessing our APIs:

[**API Keys**](#api-key-authentication)<br />
Most Loop API endpoints use API key authentication. API keys are simple to implement and work for most integration scenarios. You create API keys in the Loop Admin and include them in the `X-Authorization` header of your requests.

[**OAuth 2.0**](#oauth-2-0-authentication)<br />
OAuth 2.0 is currently **only required for accessing the Label API and Webhooks API**. For all other API endpoints, you can use API keys.

<Note>
  If you're unsure which authentication method you need, check the documentation
  for the specific endpoints you want to use.
</Note>

***

## API Key Authentication

All requests to most Loop API endpoints require an API key with the correct scopes.

### Create API Keys

1. Log in to the [Loop Admin](https://admin.loopreturns.com).
2. Navigate to **Returns Management** > **Tools & integrations** > **[Developer Tools](https://admin.loopreturns.com/settings/developers)**.
3. Click **Generate API key**, then add the scopes you want to allow the API key to access.

| Scope                          | Value                          | Available Endpoints                                                                                                                                                                                                                           |
| ------------------------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Returns                        | `returns`                      | Process Return, Remove Line Items, Cancel Return, Flag Return, Close Return, Get Return Notes, Create Return Note, Detailed Returns List, Get Return Details, Advanced Shipping Notice, Grade Items, Assess Dispositions, Create Fraud Report |
| Orders                         | `orders`                       | Create Return Deep Link, Create Return Deep Link with QR Code, List Blocklist Items, Get Blocklist Item, Create Blocklist Item, Delete Blocklist Item, List Allowlist Items, Create Allowlist Item, Get Allowlist Item, Delete Allowlist Item |
| Carts                          | `carts`                        | Create Cart, Get Cart, Update Cart, Delete Cart                                                                                                                                                                                               |
| Developer Tools                | `developer_tools`              | Get Webhooks, Create Webhook, Delete Webhook, Update Webhook                                                                                                                                                                                  |
| Destinations (Read)            | `destinations:read`            | Get All Destinations, Get Destination Details                                                                                                                                                                                                 |
| Destinations (Write)           | `destinations:write`           | Create Destination, Update Destination, Delete Destination                                                                                                                                                                                    |
| Happy Returns Shipments (Read) | `happy_returns_shipments:read` | Get Shipment Information, Get Shipments, Get Shipment Items                                                                                                                                                                                   |

<Note>
  You can also refer to the documentation for each endpoint to determine the
  required scopes.
</Note>

4. Give the API key a descriptive name, then click **Generate**.

#### Edit keys

In addition to creating API keys, you can also edit and delete keys from the Developer Tools page.

### Using API Keys

Every request requires a key to be provided in the `X-Authorization` header.

An invalid API key will result in a `401 Unauthorized` response code. An API key is invalid if or lacks the required scopes for the requested endpoint.

```json theme={null}
{
  "error": {
    "code": "401",
    "http_code": "GEN-UNAUTHORIZED",
    "message": "Unauthorized."
  }
}
```

## OAuth 2.0 Authentication

<Warning>
  OAuth 2.0 authorization is currently only required for accessing the Label API
  and Webhooks API.
</Warning>

This guide walks you through obtaining an authorization code, exchanging it for an access token, and some implementation best practices.

## Base URLs

| Service      | URL                             |
| ------------ | ------------------------------- |
| OAuth Server | `https://oauth.loopreturns.com` |
| API Server   | `https://api.loopreturns.com`   |

## Authorization Code Flow

```mermaid theme={null}
sequenceDiagram
  participant User as User
  participant "Loop Admin" as "Loop Admin"
  participant Your Integration Server as Your Integration Server
  participant oauth.loopreturns.com as OAuth Server
  participant OAuth Server as OAuth Server
  participant "Loop API" as "Loop API"

  User ->> "Loop Admin": Clicks "Install" on integration
  "Loop Admin" ->> Your Integration Server: Redirect to install_url
  Your Integration Server ->> OAuth Server: Redirect GET /authorize
  OAuth Server ->> User: Prompt for login + consent
  User ->> OAuth Server: Login + approve scopes
  OAuth Server ->> Your Integration Server: Redirect with Authorization Code
  Your Integration Server ->> OAuth Server: POST /oauth/token
  OAuth Server -->> Your Integration Server: Access Token (JWT)
  Your Integration Server ->> "Loop API": API Request with Bearer Token
  "Loop API" -->> Your Integration Server: Protected Resource
```

## Step 1: Obtaining OAuth credentials

Fill in the required fields on [this form](https://docs.google.com/forms/d/e/1FAIpQLSely4t2hGEwl0QcNzKwnc2sKqp_MhvNDQJtzUlqmuLRf8oN_A/viewform) to apply for OAuth credentials.

<Note>
  When requesting OAuth credentials in the partner form, you’ll need to submit a
  separate form for each instance (e.g., production and staging).
</Note>

After reviewing your information, Loop creates a client application for you which includes a client ID and secret. Loop will then provide you with your OAuth credentials.

### Installation URL

When filling out the registration form, you'll be asked to provide an **Installation URL**. This is the URL where Loop will redirect users when they click "Install" on your integration within the Loop Admin.

Your Installation URL should:

* Initiate the OAuth flow by redirecting users to Loop's authorization endpoint (Step 2)
* Be a secure HTTPS endpoint that you control
* *Optional:* Allow users to configure settings for your integration
* *Optional:* Create webhooks for your integration via the [Webhooks API](/api-reference/latest/programmatic-webhooks/create-webhook)

For example, if your Installation URL is `https://yourapp.com/install/loop`, when a Loop user wants to install your integration, they'll be redirected to this URL, and your application should then redirect them to the OAuth authorization endpoint to begin the credential exchange process.

<Info>
  **Important**: Loop will append an `organization` query parameter to your
  Installation URL (e.g.,
  `https://yourapp.com/install/loop?organization=acme-corp`). You must capture
  this parameter and include it in your authorization request to Loop's OAuth
  endpoint.
</Info>

## Step 2: Redirect the user for authorization

Redirect the user to Loop's authorization endpoint so they can approve your app:

```
GET https://oauth.loopreturns.com/authorize
```

### Query Parameters

| Parameter       | Required | Description                                                                                                                                                                           |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `response_type` | ✅        | Must be `code`                                                                                                                                                                        |
| `client_id`     | ✅        | Your app's client ID                                                                                                                                                                  |
| `redirect_uri`  | ✅        | Must exactly match what you registered in the Google form                                                                                                                             |
| `scope`         | ✅        | Space-separated scopes (e.g. `returns orders`). You must include `offline_access` to request a refresh token. See [Authentication](/api-reference/authentication) for more on scopes. |
| `state`         | ✅        | Random string to prevent CSRF                                                                                                                                                         |
| `organization`  | ✅        | Organization identifier passed from Loop                                                                                                                                              |
| `audience`      | ✅        | Must be `https://api.loopreturns.com/api`                                                                                                                                             |

### Example

```uri theme={null}
https://oauth.loopreturns.com/authorize?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=https://yourapp.com/callback
  &scope=labels:read labels:write offline_access
  &state=random123
  &organization=acme-corp
  &audience=https://api.loopreturns.com/api
```

After the user approves access, they'll be redirected to your redirect URI like this:

```uri theme={null}
https://yourapp.com/callback?code=AUTH_CODE&state=random123
```

## Step 3: Exchange the code for an access token

Once you receive the authorization code, exchange it for an access token using the following endpoint:

<Warning>
  **Critical**: This step must be performed on your secure backend server, never
  in client-side code (browser, mobile app, etc.). The `client_secret` is highly
  sensitive and should never be exposed to end users.
</Warning>

```
POST https://oauth.loopreturns.com/oauth/token
```

### Body Parameters

| Parameter       | Required | Description                              |
| --------------- | -------- | ---------------------------------------- |
| `grant_type`    | ✅        | Must be `authorization_code`             |
| `code`          | ✅        | The code received from the previous step |
| `redirect_uri`  | ✅        | Must match the original redirect URI     |
| `client_id`     | ✅        | Your app's client ID                     |
| `client_secret` | ✅        | Your app's client secret                 |

### Example

<CodeGroup>
  ```curl cURL theme={null}
  curl -X POST https://oauth.loopreturns.com/oauth/token
    -H "Content-Type: application/x-www-form-urlencoded"
    -d "grant_type=authorization_code"
    -d "code=AUTH_CODE"
    -d "redirect_uri=https://yourapp.com/callback"
    -d "client_id=YOUR_CLIENT_ID"
    -d "client_secret=YOUR_CLIENT_SECRET"
  ```

  ```python Python theme={null}
  import requests

  url = "https://oauth.loopreturns.com/oauth/token"
  data = {
      "grant_type": "authorization_code",
      "code": "AUTH_CODE",
      "redirect_uri": "https://yourapp.com/callback",
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET"
  }
  headers = {"Content-Type": "application/x-www-form-urlencoded"}

  response = requests.post(url, data=data, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const url = "https://oauth.loopreturns.com/oauth/token";
  const data = new URLSearchParams({
    grant_type: "authorization_code",
    code: "AUTH_CODE",
    redirect_uri: "https://yourapp.com/callback",
    client_id: "YOUR_CLIENT_ID",
    client_secret: "YOUR_CLIENT_SECRET",
  });

  fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: data,
  })
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error("Error:", error));
  ```

  ```php PHP theme={null}
  <?php

  $url = 'https://oauth.loopreturns.com/oauth/token';
  $data = [
      'grant_type' => 'authorization_code',
      'code' => 'AUTH_CODE',
      'redirect_uri' => 'https://yourapp.com/callback',
      'client_id' => 'YOUR_CLIENT_ID',
      'client_secret' => 'YOUR_CLIENT_SECRET'
  ];

  $options = [
      'http' => [
          'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
          'method' => 'POST',
          'content' => http_build_query($data)
      ]
  ];

  $context = stream_context_create($options);
  $result = file_get_contents($url, false, $context);
  echo $result;
  ```

  ```go Go theme={null}
  package main

  import (
      "fmt"
      "net/http"
      "net/url"
      "strings"
  )

  func main() {
      tokenURL := "https://oauth.loopreturns.com/oauth/token"
      data := url.Values{}
      data.Set("grant_type", "authorization_code")
      data.Set("code", "AUTH_CODE")
      data.Set("redirect_uri", "https://yourapp.com/callback")
      data.Set("client_id", "YOUR_CLIENT_ID")
      data.Set("client_secret", "YOUR_CLIENT_SECRET")

      client := &http.Client{}
      req, err := http.NewRequest("POST", tokenURL, strings.NewReader(data.Encode()))
      if err != nil {
          fmt.Println(err)
          return
      }

      req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
      resp, err := client.Do(req)
      if err != nil {
          fmt.Println(err)
          return
      }
      defer resp.Body.Close()
  }
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import java.net.URLEncoder;
  import java.nio.charset.StandardCharsets;

  public class OAuthToken {
      public static void main(String[] args) throws Exception {
          String tokenURL = "https://oauth.loopreturns.com/oauth/token";

          String formData = String.format(
              "grant_type=authorization_code" +
              "&code=%s" +
              "&redirect_uri=%s" +
              "&client_id=%s" +
              "&client_secret=%s",
              URLEncoder.encode("AUTH_CODE", StandardCharsets.UTF_8),
              URLEncoder.encode("https://yourapp.com/callback", StandardCharsets.UTF_8),
              URLEncoder.encode("YOUR_CLIENT_ID", StandardCharsets.UTF_8),
              URLEncoder.encode("YOUR_CLIENT_SECRET", StandardCharsets.UTF_8)
          );

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(tokenURL))
              .header("Content-Type", "application/x-www-form-urlencoded")
              .POST(HttpRequest.BodyPublishers.ofString(formData))
              .build();

          HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
          System.out.println(response.body());
      }
  }
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "access_token": "ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "REFRESH_TOKEN",
  "scope": "labels:read labels:write offline_access"
}
```

<Info>
  **Token Expiration**: Access tokens have a TTL (time to live) of 1 hour (3600
  seconds). The `expires_in` field in the response indicates the number of
  seconds until the token expires. You'll need to use the refresh token to
  obtain a new access token before it expires.
</Info>

Once you've received the OAuth access token, use the access token to authenticate your API
requests by passing it in the `Authorization` header.

## Refreshing your access token

Access tokens expire after 1 hour. When your token expires, call the `POST https://oauth.loopreturns.com/oauth/token` endpoint with `refresh_token` in the `grant_type` parameter to obtain a new token.

### Body Parameters

| Parameter       | Required | Description                       |
| --------------- | -------- | --------------------------------- |
| `grant_type`    | ✅        | Must be `refresh_token`           |
| `refresh_token` | ✅        | The token you previously received |
| `client_id`     | ✅        | Your app's client ID              |
| `client_secret` | ✅        | Your app's client secret          |

### Example

<CodeGroup>
  ```curl cURL theme={null}
  curl -X POST https://oauth.loopreturns.com/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=refresh_token" \
    -d "refresh_token=REFRESH_TOKEN" \
    -d "client_id=YOUR_CLIENT_ID" \
    -d "client_secret=YOUR_CLIENT_SECRET"
  ```

  ```python Python theme={null}
  import requests

  url = "https://oauth.loopreturns.com/oauth/token"
  data = {
      "grant_type": "refresh_token",
      "refresh_token": "REFRESH_TOKEN",
      "client_id": "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET"
  }
  headers = {"Content-Type": "application/x-www-form-urlencoded"}

  response = requests.post(url, data=data, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const url = "https://oauth.loopreturns.com/oauth/token";
  const data = new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: "REFRESH_TOKEN",
    client_id: "YOUR_CLIENT_ID",
    client_secret: "YOUR_CLIENT_SECRET",
  });

  fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: data,
  })
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error("Error:", error));
  ```

  ```php PHP theme={null}
  <?php

  $url = 'https://oauth.loopreturns.com/oauth/token';
  $data = [
      'grant_type' => 'refresh_token',
      'refresh_token' => 'REFRESH_TOKEN',
      'client_id' => 'YOUR_CLIENT_ID',
      'client_secret' => 'YOUR_CLIENT_SECRET'
  ];

  $options = [
      'http' => [
          'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
          'method' => 'POST',
          'content' => http_build_query($data)
      ]
  ];

  $context = stream_context_create($options);
  $result = file_get_contents($url, false, $context);
  echo $result;
  ```

  ```go Go theme={null}
  package main

  import (
      "fmt"
      "net/http"
      "net/url"
      "strings"
  )

  func main() {
      tokenURL := "https://oauth.loopreturns.com/oauth/token"
      data := url.Values{}
      data.Set("grant_type", "refresh_token")
      data.Set("refresh_token", "REFRESH_TOKEN")
      data.Set("client_id", "YOUR_CLIENT_ID")
      data.Set("client_secret", "YOUR_CLIENT_SECRET")

      client := &http.Client{}
      req, err := http.NewRequest("POST", tokenURL, strings.NewReader(data.Encode()))
      if err != nil {
          fmt.Println(err)
          return
      }

      req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
      resp, err := client.Do(req)
      if err != nil {
          fmt.Println(err)
          return
      }
      defer resp.Body.Close()
  }
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import java.net.URLEncoder;
  import java.nio.charset.StandardCharsets;

  public class RefreshToken {
      public static void main(String[] args) throws Exception {
          String tokenURL = "https://oauth.loopreturns.com/oauth/token";

          String formData = String.format(
              "grant_type=refresh_token" +
              "&refresh_token=%s" +
              "&client_id=%s" +
              "&client_secret=%s",
              URLEncoder.encode("REFRESH_TOKEN", StandardCharsets.UTF_8),
              URLEncoder.encode("YOUR_CLIENT_ID", StandardCharsets.UTF_8),
              URLEncoder.encode("YOUR_CLIENT_SECRET", StandardCharsets.UTF_8)
          );

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(tokenURL))
              .header("Content-Type", "application/x-www-form-urlencoded")
              .POST(HttpRequest.BodyPublishers.ofString(formData))
              .build();

          HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
          System.out.println(response.body());
      }
  }
  ```
</CodeGroup>

## Troubleshooting

<Warning>
  Ensure your <code>redirect\_uri</code> exactly matches what you registered —
  including trailing slashes!
</Warning>

* Double-check your `client_secret` if you're getting 401s from the token endpoint.
* Always verify the `state` parameter to prevent CSRF attacks.

## Test your integration

Once you've received an OAuth token, call the endpoint below to verify that your token is working:

<CodeGroup>
  ```curl cURL theme={null}
  curl https://api.loopreturns.com/api/v1/auth/me \
    -H "Authorization: Bearer ACCESS_TOKEN"
  ```

  ```python Python theme={null}
  import requests

  url = "https://api.loopreturns.com/api/v1/auth/me"
  headers = {
      "Authorization": "Bearer ACCESS_TOKEN"
  }

  response = requests.get(url, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const url = "https://api.loopreturns.com/api/v1/auth/me";

  fetch(url, {
    headers: {
      Authorization: "Bearer ACCESS_TOKEN",
    },
  })
    .then((response) => response.json())
    .then((data) => console.log(data))
    .catch((error) => console.error("Error:", error));
  ```

  ```php PHP theme={null}
  <?php

  $url = 'https://api.loopreturns.com/api/v1/auth/me';
  $options = [
      'http' => [
          'header' => "Authorization: Bearer ACCESS_TOKEN\r\n",
          'method' => 'GET'
      ]
  ];

  $context = stream_context_create($options);
  $result = file_get_contents($url, false, $context);
  echo $result;
  ```

  ```go Go theme={null}
  package main

  import (
      "fmt"
      "io"
      "net/http"
  )

  func main() {
      url := "https://api.loopreturns.com/api/v1/auth/me"

      client := &http.Client{}
      req, err := http.NewRequest("GET", url, nil)
      if err != nil {
          fmt.Println(err)
          return
      }

      req.Header.Add("Authorization", "Bearer ACCESS_TOKEN")
      resp, err := client.Do(req)
      if err != nil {
          fmt.Println(err)
          return
      }
      defer resp.Body.Close()

      body, err := io.ReadAll(resp.Body)
      if err != nil {
          fmt.Println(err)
          return
      }
      fmt.Println(string(body))
  }
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;

  public class TestIntegration {
      public static void main(String[] args) throws Exception {
          String url = "https://api.loopreturns.com/api/v1/auth/me";

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(url))
              .header("Authorization", "Bearer ACCESS_TOKEN")
              .GET()
              .build();

          HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
          System.out.println(response.body());
      }
  }
  ```
</CodeGroup>

## Available OAuth Scopes

When requesting authorization, you must specify the scopes your application needs. Scopes define what resources your application can access on behalf of the user.

### Scope Format

OAuth scopes follow the pattern: `{resource}:{action}`

* **Resources**: API resource categories
* **Actions**: `read` (view data) or `write` (create/modify data)

### Available Scopes

| Scope                  | Description                                               |
| ---------------------- | --------------------------------------------------------- |
| `labels:read`          | Read access to labels                                     |
| `labels:write`         | Create and modify labels                                  |
| `label_requests:read`  | Read access to label requests                             |
| `label_requests:write` | Create and modify label requests                          |
| `developer_tools`      | Create and modify webhooks                                |
| `offline_access`       | Required to receive a refresh token for long-lived access |

### Scope Selection Best Practices

<Tip>
  **Principle of Least Privilege**: Only request the minimum scopes necessary
  for your integration to function. This improves security and user trust.
</Tip>

* **Start minimal**: Begin with read-only scopes and add write permissions only when needed
* **Be specific**: Request scopes for specific resources rather than broad permissions

## Error Handling

Understanding and properly handling OAuth errors is crucial for a robust integration. This section covers all possible error scenarios and how to handle them.

### OAuth Authorization Errors

The possible error codes follow the OAuth 2.0 specification as defined in [RFC 6749 Section 5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2). Common error codes include `invalid_request`, `unauthorized_client`, `access_denied`, `unsupported_response_type`, `invalid_scope`, `server_error`, and `temporarily_unavailable`.

#### Authorization Error Response Format

When an error occurs during authorization, the user is redirected to your `redirect_uri` with error parameters:

```uri theme={null}
https://yourapp.com/callback?error=access_denied
  &error_description=The+user+denied+the+request
  &state=random123
```

## Security & Token Management Best Practices

Proper security and token management are critical for protecting user data and maintaining trust. Follow these best practices to secure your OAuth implementation.

### Security Best Practices

#### Client Secret Protection

<Warning>
  **Critical**: Never expose your `client_secret` in client-side code, mobile
  apps, or public repositories.
</Warning>

**✅ Do:**

* Store client secrets in environment variables or secure configuration management
* Use the client secret only on your secure backend servers
* Use different credentials for development, staging, and production environments

**❌ Don't:**

* Include client secrets in frontend JavaScript, mobile apps, or any client-side code
* Commit client secrets to version control
* Log client secrets in application logs
* Share client secrets via email or insecure channels

<CodeGroup>
  ```python Python theme={null}
  import os
  from django.conf import settings

  # ✅ Correct: Using environment variables
  CLIENT_SECRET = os.environ.get('LOOP_CLIENT_SECRET')

  # ✅ Correct: Using Django settings
  CLIENT_SECRET = settings.LOOP_CLIENT_SECRET

  # ❌ Wrong: Hardcoded in source code
  CLIENT_SECRET = "your_actual_client_secret_here"
  ```

  ```javascript JavaScript theme={null}
  // ✅ Correct: Using environment variables (Node.js)
  const CLIENT_SECRET = process.env.LOOP_CLIENT_SECRET;

  // ✅ Correct: Using a configuration file (not committed to git)
  const config = require("./config/secrets.json");
  const CLIENT_SECRET = config.loop_client_secret;

  // ❌ Wrong: Hardcoded in source code
  const CLIENT_SECRET = "your_actual_client_secret_here";
  ```
</CodeGroup>

#### HTTPS Requirements

<Warning>
  All OAuth endpoints and your application endpoints must use HTTPS in
  production.
</Warning>

* **Authorization URLs**: Must use HTTPS to prevent code interception
* **Redirect URIs**: Must use HTTPS to protect authorization codes
* **Token exchanges**: Must use HTTPS to protect client credentials
* **API requests**: Must use HTTPS to protect access tokens

#### State Parameter Security

The `state` parameter prevents CSRF attacks and should be cryptographically secure:

<CodeGroup>
  ```python Python theme={null}
  import secrets
  import hashlib

  def generate_secure_state():
      # Generate a cryptographically secure random string
      return secrets.token_urlsafe(32)

  def verify_state(received_state, expected_state):
      # Use constant-time comparison to prevent timing attacks
      return secrets.compare_digest(received_state, expected_state)

  # Usage
  state = generate_secure_state()
  # Store state in session for later verification
  request.session['oauth_state'] = state
  ```

  ```javascript JavaScript theme={null}
  const crypto = require("crypto");

  function generateSecureState() {
    // Generate a cryptographically secure random string
    return crypto.randomBytes(32).toString("base64url");
  }

  function verifyState(receivedState, expectedState) {
    // Use constant-time comparison
    return crypto.timingSafeEqual(
      Buffer.from(receivedState),
      Buffer.from(expectedState)
    );
  }

  // Usage
  const state = generateSecureState();
  // Store state in session for later verification
  req.session.oauthState = state;
  ```
</CodeGroup>
