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

# Partner Handshake

> POST /v1/partner/handshake - Authenticate users and receive session tokens for iframe embedding

## Endpoint

```
POST /v1/partner/handshake
```

**Base URL:** `https://api.usesticker.com`

**Staging/Sandbox URL:** `https://staging.api.usesticker.com`

## Description

Creates an authentication session for a user to access the embedded Sticker iframe. Returns a secure, time-limited session token and the iframe embed URL.

**Workflow:**

1. Partner authenticates with API key
2. Partner provides `internal_user_id` or `profile_id` to identify the user
3. Sticker finds the profile and validates it belongs to the partner
4. Sticker generates a temporary session token (5 minute expiry, single-use)
5. Returns session token + complete iframe embed URL

## Authentication

Requires Partner API key in the `X-API-Key` header:

```bash theme={null}
X-API-Key: sk_live_your_api_key_here
```

## Request Body

You must provide either `internal_user_id` OR `profile_id`:

```json theme={null}
{
  "internal_user_id": "user-789"
}
```

OR

```json theme={null}
{
  "profile_id": "660f9500-f30c-52e5-b827-557766551111"
}
```

## Request Parameters

<ParamField path="internal_user_id" type="string">
  Your internal identifier for the user (provided during organization setup). **Either this OR `profile_id` is required.**
</ParamField>

<ParamField path="profile_id" type="string">
  Sticker's unique identifier for the user profile (UUID). **Either this OR `internal_user_id` is required.**
</ParamField>

<Info>
  Using `internal_user_id` is recommended as it uses your system's identifiers and doesn't require storing Sticker profile IDs.
</Info>

## Response

### Success Response (200 OK)

```json theme={null}
{
  "success": true,
  "session_key": "a1b2c3d4e5f6789012345678901234567890123456789012345678901234",
  "iframe_embed_url": "https://shop.usesticker.com/embedded/550e8400-e29b-41d4-a716-446655440000?session_key=a1b2c3d4e5f6789012345678901234567890123456789012345678901234",
  "expires_at": "2024-01-15T10:35:00.000Z",
  "profile": {
    "id": "660f9500-f30c-52e5-b827-557766551111",
    "first_name": "Jane",
    "last_name": "Smith",
    "email": "jane@acmemedical.com"
  }
}
```

### Response Fields

<ResponseField name="success" type="boolean">
  Whether the handshake succeeded.
</ResponseField>

<ResponseField name="session_key" type="string">
  Secure, time-limited token for authenticating the user in the iframe. 64-character hex string.
</ResponseField>

<ResponseField name="iframe_embed_url" type="string">
  Complete URL to embed in an iframe. Includes the partner ID and session key already appended.
</ResponseField>

<ResponseField name="expires_at" type="string">
  ISO 8601 timestamp when the session token expires. Tokens expire **5 minutes** after generation.
</ResponseField>

<ResponseField name="profile" type="object">
  Basic profile information for the authenticated user.
</ResponseField>

<ResponseField name="profile.id" type="string">
  Sticker's unique identifier for the profile (UUID).
</ResponseField>

<ResponseField name="profile.first_name" type="string">
  User's first name.
</ResponseField>

<ResponseField name="profile.last_name" type="string">
  User's last name.
</ResponseField>

<ResponseField name="profile.email" type="string">
  User's email address.
</ResponseField>

## Error Responses

<AccordionGroup>
  <Accordion title="400 Invalid Request">
    ```json theme={null}
    {
      "error": "Invalid request body",
      "details": [
        {
          "code": "custom",
          "message": "Either internal_user_id or profile_id must be provided",
          "path": []
        }
      ]
    }
    ```

    **Common causes:**

    * Neither `internal_user_id` nor `profile_id` provided
    * Invalid UUID format for `profile_id`
  </Accordion>

  <Accordion title="401 Unauthorized">
    ```json theme={null}
    {
      "error": "Unauthorized",
      "message": "Invalid or missing API key"
    }
    ```

    **Solution:** Check your API key is correct and included in the `X-API-Key` header.
  </Accordion>

  <Accordion title="404 Profile Not Found">
    ```json theme={null}
    {
      "error": "Profile not found",
      "details": "No profile found with the provided credentials for this partner"
    }
    ```

    **Solution:** Ensure the user was created via the organization setup endpoint first. Verify the `internal_user_id` or `profile_id` is correct.
  </Accordion>

  <Accordion title="400 Profile Not Set Up">
    ```json theme={null}
    {
      "error": "Profile not set up for authentication",
      "details": "Profile must be linked to an auth user. Please complete setup first."
    }
    ```

    **Solution:** The profile exists but is not properly linked to an auth user. Contact Sticker support.
  </Accordion>

  <Accordion title="500 Session Creation Failed">
    ```json theme={null}
    {
      "error": "Failed to create authentication session",
      "details": "Database error message"
    }
    ```

    **Solution:** Retry with exponential backoff. Contact support if the error persists.
  </Accordion>
</AccordionGroup>

## Session Token Properties

Session tokens have specific security properties:

| Property          | Description                                      |
| ----------------- | ------------------------------------------------ |
| **Single-use**    | Invalidated immediately after first use          |
| **Time-limited**  | Expires 5 minutes after generation               |
| **User-bound**    | Tied to a specific user profile                  |
| **Partner-bound** | Can only be used with the partner's iframe URL   |
| **Secure**        | 64-character cryptographically random hex string |

<Warning>
  **Never reuse session tokens.** Generate a new token each time the user opens the supplies module, even if they just closed it seconds ago.
</Warning>

## Code Examples

<CodeGroup>
  ```javascript Node.js theme={null}
  async function getHandshakeToken(userId) {
    const response = await fetch('https://api.usesticker.com/v1/partner/handshake', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.STICKER_API_KEY,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        internal_user_id: userId
      })
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Handshake failed: ${error.error}`);
    }

    return await response.json();
  }

  // Usage
  const { session_key, iframe_embed_url } = await getHandshakeToken('user-789');
  console.log('Embed URL:', iframe_embed_url);
  ```

  ```python Python theme={null}
  import httpx
  import os

  async def get_handshake_token(user_id: str) -> dict:
      async with httpx.AsyncClient() as client:
          response = await client.post(
              "https://api.usesticker.com/v1/partner/handshake",
              headers={
                  "X-API-Key": os.getenv("STICKER_API_KEY"),
                  "Content-Type": "application/json",
              },
              json={"internal_user_id": user_id}
          )
          
          response.raise_for_status()
          return response.json()

  # Usage
  result = await get_handshake_token("user-789")
  print(f"Embed URL: {result['iframe_embed_url']}")
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.usesticker.com/v1/partner/handshake \
    -H "X-API-Key: sk_live_your_api_key" \
    -H "Content-Type: application/json" \
    -d '{"internal_user_id": "user-789"}'
  ```
</CodeGroup>

## Using the Session Token

After receiving the handshake response, embed the iframe using the `iframe_embed_url`:

```jsx theme={null}
// React example
function SuppliesModule({ userId }) {
  const [iframeUrl, setIframeUrl] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function authenticate() {
      const response = await fetch('/api/supplies/auth', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ userId })
      });
      
      const { iframe_url } = await response.json();
      setIframeUrl(iframe_url);
      setLoading(false);
    }
    
    authenticate();
  }, [userId]);

  if (loading) return <div>Loading supplies...</div>;

  return (
    <iframe
      src={iframeUrl}
      className="w-full h-full border-0"
      title="Sticker Embedded Procurement"
      sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"
      allow="payment; publickey-credentials-get; fullscreen"
    />
  );
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Generate on Demand" icon="bolt">
    Create session tokens only when the user clicks to open the supplies module. Don't pre-generate tokens.
  </Accordion>

  <Accordion title="Handle Expiration" icon="hourglass">
    If the iframe fails to load (e.g., user's connection is slow), generate a fresh token and retry.
  </Accordion>

  <Accordion title="Never Cache" icon="ban">
    Don't store or cache session tokens. They're single-use and short-lived.
  </Accordion>

  <Accordion title="Server-Side Only" icon="server">
    Always call the handshake endpoint from your backend. Never expose your API key in client-side code.
  </Accordion>

  <Accordion title="Error Recovery" icon="arrows-rotate">
    If the handshake fails, show an error message and offer a retry button that generates a new token.
  </Accordion>
</AccordionGroup>

## Related

<CardGroup cols={2}>
  <Card title="Organization Setup" icon="building" href="/api-reference/organization-setup">
    Provision organizations and users before handshake
  </Card>

  <Card title="iframe Embedding" icon="window" href="/integration/iframe-embedding">
    Best practices for embedding the Sticker iframe
  </Card>
</CardGroup>
