> ## 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.

# User Handshake

> Authenticating users and receiving session tokens for iframe embedding

## Overview

The Handshake endpoint is called **every time** a user opens the supplies module in your platform. It authenticates the user and returns a secure, time-limited session token that you use to embed the Sticker iframe.

## When to Call This Endpoint

<Steps>
  <Step title="User Clicks Supplies">
    User navigates to the supplies/procurement section in your platform
  </Step>

  <Step title="Get Current User">
    Identify the authenticated user from your session
  </Step>

  <Step title="Call Handshake API">
    Send user identifier to receive a session token
  </Step>

  <Step title="Embed iframe">
    Display the iframe using the returned URL
  </Step>
</Steps>

## API Endpoint

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

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

## Authentication

Use the `X-API-Key` header (not `Authorization: Bearer`):

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

## Request Format

Send 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"
}
```

<Info>
  **Recommended:** Use `internal_user_id` (your internal identifier from organization setup). This way you don't need to store Sticker profile IDs in your database.
</Info>

## Response Format

```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

| Field              | Description                                      |
| ------------------ | ------------------------------------------------ |
| `session_key`      | 64-char hex token for authentication             |
| `iframe_embed_url` | Complete URL to embed—use this directly!         |
| `expires_at`       | When the token expires (5 minutes from creation) |
| `profile`          | Basic info about the authenticated user          |

## Session Token Properties

Session tokens have important security properties:

| Property            | Description                               |
| ------------------- | ----------------------------------------- |
| **Single-use**      | Token is invalidated after first use      |
| **5 minute expiry** | Token expires 5 minutes after creation    |
| **User-bound**      | Tied to a specific user profile           |
| **Partner-bound**   | Can only be used with your partner iframe |

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

## Complete Flow Example

Here's what a typical integration looks like:

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Your Frontend
    participant Your Backend
    participant Sticker API
    
    User->>Your Frontend: Click "Supplies"
    Your Frontend->>Your Backend: POST /api/supplies/auth
    Your Backend->>Sticker API: POST /v1/partner/handshake
    Sticker API-->>Your Backend: { session_key, iframe_embed_url }
    Your Backend-->>Your Frontend: { iframe_url }
    Your Frontend->>User: Render iframe with URL
```

## Code Examples

### Your Backend Endpoint

<CodeGroup>
  ```javascript Node.js/Express theme={null}
  app.post('/api/supplies/auth', async (req, res) => {
    const { userId } = req.body;
    
    // Verify user is authenticated in YOUR system
    const currentUser = await getAuthenticatedUser(req);
    if (!currentUser || currentUser.id !== userId) {
      return res.status(401).json({ error: 'Unauthorized' });
    }
    
    // Call Sticker handshake
    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();
      console.error('Handshake failed:', error);
      return res.status(response.status).json({ error: 'Authentication failed' });
    }
    
    const data = await response.json();
    
    // Return the iframe URL to your frontend
    res.json({
      iframe_url: data.iframe_embed_url,
      expires_at: data.expires_at
    });
  });
  ```

  ```python Python/FastAPI theme={null}
  from fastapi import FastAPI, Request, HTTPException
  import httpx
  import os

  @app.post("/api/supplies/auth")
  async def supplies_auth(request: Request, user_id: str):
      # Verify user is authenticated in YOUR system
      current_user = await get_authenticated_user(request)
      if not current_user or current_user.id != user_id:
          raise HTTPException(status_code=401, detail="Unauthorized")
      
      # Call Sticker handshake
      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}
          )
          
          if not response.is_success:
              raise HTTPException(
                  status_code=response.status_code, 
                  detail="Authentication failed"
              )
          
          data = response.json()
          
          return {
              "iframe_url": data["iframe_embed_url"],
              "expires_at": data["expires_at"]
          }
  ```

  ```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>

### Your Frontend Component

```jsx theme={null}
// React component that loads the supplies iframe
import { useState, useEffect } from 'react';

function SuppliesModule({ userId }) {
  const [iframeUrl, setIframeUrl] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function authenticate() {
      try {
        setLoading(true);
        setError(null);
        
        // Call YOUR backend (not Sticker directly!)
        const response = await fetch('/api/supplies/auth', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ userId })
        });
        
        if (!response.ok) {
          throw new Error('Failed to authenticate');
        }
        
        const { iframe_url } = await response.json();
        setIframeUrl(iframe_url);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    
    authenticate();
  }, [userId]);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} onRetry={() => window.location.reload()} />;

  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"
    />
  );
}
```

## Error Handling

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

    **Solution:** Include either `internal_user_id` or `profile_id` in the request body.
  </Accordion>

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

    **Solution:** Check `X-API-Key` header (not `Authorization: Bearer`)
  </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 organization setup was called first
    * Verify the `internal_user_id` matches what was used in setup
    * Check you're using the correct partner API key
  </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:** Contact Sticker support—the profile exists but isn't properly linked.
  </Accordion>
</AccordionGroup>

## Best Practices

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

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

    ```
    ✅ Frontend → Your Backend → Sticker API
    ❌ Frontend → Sticker API directly
    ```
  </Accordion>

  <Accordion title="Handle Expiration" icon="clock">
    If a user takes too long to load the page (>5 min), generate a fresh token:

    ```javascript theme={null}
    // Track when token was generated
    const tokenGeneratedAt = Date.now();

    // On iframe error, check if token might be expired
    if (Date.now() - tokenGeneratedAt > 4 * 60 * 1000) {
      // Token is probably expired, get a new one
      await reauthenticate();
    }
    ```
  </Accordion>

  <Accordion title="Error Recovery" icon="rotate">
    If the iframe fails to load, show a retry button that generates a new token:

    ```jsx theme={null}
    if (error) {
      return (
        <div>
          <p>Failed to load supplies</p>
          <button onClick={authenticate}>Retry</button>
        </div>
      );
    }
    ```
  </Accordion>
</AccordionGroup>

<Frame caption="The embedded Sticker experience after successful authentication">
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/sticker-c03065e9/placeholder-authenticated-screenshot.png" alt="Authenticated Sticker" />
</Frame>

## Testing

Test the handshake flow:

1. **Setup a test user** via organization setup endpoint
2. **Call handshake** with the test user's `internal_user_id`
3. **Verify response** contains valid `iframe_embed_url`
4. **Open the URL** in a browser to confirm authentication works

```bash theme={null}
# Test the handshake
curl -X POST https://api.usesticker.com/v1/partner/handshake \
  -H "X-API-Key: sk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{"internal_user_id": "test-user-123"}'
```

## Next Steps

<CardGroup cols={2}>
  <Card title="iframe Embedding" icon="window" href="/integration/iframe-embedding">
    Learn how to embed and style the iframe
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference/handshake">
    Complete API specification
  </Card>
</CardGroup>
