Skip to main content
Auto-generated from autoplay_sdk/skills/. Edit the source SKILL.md and run python scripts/sync_skill_docs.py.
Copy the block below and paste it into Claude, Cursor, or any AI agent to run the full Amplitude integration autonomously.
---
name: activity-amplitude
description: >-
  Configures Amplitude as the event source for Autoplay using the @amplitude/unified SDK.
  Installs the SDK, initializes it with autocapture and session replay, calls
  amplitude.setUserId() and amplitude.identify() with product_id on login, creates a
  server-side proxy for the identify call so credentials stay server-side, runs the
  Autoplay registration script to get ingest/stream URLs, and guides the customer through
  creating an Amplitude HTTP Event Streaming destination with the Freemarker template.
  Also patches Content-Security-Policy headers if the project uses them.
disable-model-invocation: true
---

# Autoplay ร— Amplitude Integration Guide

This guide is structured for an AI agent. Steps marked **๐Ÿค– AGENT** are handled autonomously. Steps marked **๐Ÿง‘ HUMAN** require customer input.

---

## Step 1 โ€” Understand the project

**๐Ÿค– AGENT**

Before writing any code, read the project to determine:

- **Frontend framework** โ€” Next.js, Nuxt, Angular, Vue, SvelteKit, plain React, etc.
- **Auth system** โ€” what library handles login/logout and how to get the current user and session ID
- **Env var conventions** โ€” how the project names and exposes environment variables (e.g. `VITE_`, `NEXT_PUBLIC_`, `NG_APP_`, no prefix, etc.)
- **App entry point** โ€” where to initialize SDKs (e.g. `main.ts`, `app.module.ts`, `_app.tsx`, `main.js`)
- **Package manager** โ€” npm, yarn, pnpm, bun

Do not write any code until this is understood.

---

## Step 2 โ€” Install the Amplitude SDK

**๐Ÿค– AGENT**

Check if `@amplitude/unified` is already in the project's dependencies. If not, install it:

    npm install @amplitude/unified
    # or yarn / pnpm / bun equivalent

---

## Step 3 โ€” Get Amplitude credentials

### ๐Ÿง‘ HUMAN โ€” Amplitude API Key

Ask the customer to open their Amplitude project and copy the API Key.
Walkthrough: https://app.arcade.software/share/ycesTzu0o9kI2N56wytr

### ๐Ÿง‘ HUMAN โ€” Amplitude Project ID

Ask the customer to go to Settings โ†’ Projects โ†’ [their project] and copy the numeric Project ID.
Walkthrough: https://app.arcade.software/share/gbMSMIenMpBYhCWbLFrh

---

## Step 4 โ€” Register with Autoplay

**๐Ÿค– AGENT**

    python3 -m venv .venv && source .venv/bin/activate
    pip install autoplay-sdk

    import asyncio
    from autoplay_sdk.admin import onboard_product
    from autoplay_sdk.providers import AmplitudeProvider

    async def main():
        result = await onboard_product(
            "<AMPLITUDE_PROJECT_ID>",
            contact_email="<customer email>",
            user_activity_provider=AmplitudeProvider(),
            print_operator_summary=True,
        )

    asyncio.run(main())

The script prints three values needed in Steps 5 and 6:
- amplitude_ingest_url  โ†’  Amplitude dashboard HTTP destination URL
- stream_url            โ†’  Identify endpoint called from the app
- unkey_key             โ†’  Bearer token for both

Add to the project's env file. Client-side values (API Key, Product ID) use the framework's public prefix. The rest stay server-side.

---

## Step 5 โ€” Wire Amplitude into the app

**๐Ÿค– AGENT**

### Initialize at app startup

    import * as amplitude from '@amplitude/unified';

    amplitude.initAll('<AMPLITUDE_API_KEY>', {
      analytics: { autocapture: true },
      sessionReplay: { sampleRate: 1 },
    });

autocapture: true is required.

### Identify on login

    amplitude.setUserId(<user.id>);

    const identifyObj = new amplitude.Identify();
    identifyObj.set('product_id', '<AUTOPLAY_PRODUCT_ID>');
    identifyObj.set('email', <user.email>);
    identifyObj.set('name', <user.name>);
    amplitude.identify(identifyObj);

    const sessionId = amplitude.getSessionId();
    if (sessionId) {
      fetch('/api/amplitude-identify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email: <user.email>,
          session_id: String(sessionId),
          product_id: '<AUTOPLAY_PRODUCT_ID>',
        }),
      });
    }

### Server-side proxy for the identify call

Create a backend endpoint (Next.js route handler, Express, Nuxt server route, etc.) that forwards:

    POST <stream_url>/identify
    Content-Type: application/json
    { email, session_id, product_id }

No auth header needed for the identify call.

### Reset on logout

    amplitude.reset();

---

## Step 6 โ€” Create the Amplitude Event Streaming destination

### ๐Ÿง‘ HUMAN

1. Go to Data โ†’ Destinations โ†’ Add Destination โ†’ HTTP โ†’ Event Streaming
2. Endpoint URL: amplitude_ingest_url from Step 4
   Authentication: Bearer Token
   Token: unkey_key from Step 4
   Events to send: All events
3. Paste this Freemarker template in the Event Body section exactly:

<#setting number_format="0.####">
<#assign et = input.event_type!''>
<#assign ep = input.event_properties!{}>
<#assign up = input.user_properties!{}>
{
  "events": [{
    "event_type": "<#if et?starts_with('Viewed') || et == '[Amplitude] Page Viewed' || et == 'Page Viewed'>$pageview<#elseif et == '[Amplitude] Element Clicked' || et == '[Amplitude] Element Changed' || et?starts_with('Form')>$autocapture<#else>${et}</#if>",
    "user_id": "${input.user_id!''}",
    "device_id": "${input.device_id!''}",
    "session_id": ${input.session_id!0},
    "time": ${input.time!0},
    "user_properties": { <#list up?keys as k>"${k}": "${up[k]}"<#sep>, </#sep></#list> },
    "event_properties": {
      "[Amplitude] Page URL": "${ep['Page URL']!ep['[Amplitude] Page URL']!''}",
      "[Amplitude] Page Title": "${ep['Page Title']!ep['[Amplitude] Page Title']!''}",
      "$event_type": "<#if et == '[Amplitude] Element Changed'>change<#elseif et?starts_with('Form Submitted')>submit<#elseif et?starts_with('Form Started')>focus<#else>click</#if>",
      "$current_url": "${ep['Page URL']!ep['[Amplitude] Page URL']!''}",
      "$button_text": "${ep['[Amplitude] Element Text']!ep['Element Text']!''}",
      "$elements_chain": "${ep['[Amplitude] Element Path']!ep['[Amplitude] Element Hierarchy']!''}",
      "$element_id": "${ep['[Amplitude] Element ID']!ep['Element ID']!''}",
      "$element_tag": "${ep['[Amplitude] Element Tag']!ep['Element Tag']!''}"
    }
  }]
}

Walkthrough: https://app.arcade.software/share/NeCcl6dtC7DQdjOjue28

---

## Step 7 โ€” Verify

**๐Ÿค– AGENT**

Ask the customer to log in and navigate around. Then verify:

    curl -N "<stream_url>" -H "Authorization: Bearer <unkey_key>"

Session activity should appear within seconds.

---

## Content Security Policy (CSP)

**๐Ÿค– AGENT**

If the project sets a Content-Security-Policy header, add:
- connect-src: https://*.amplitude.com
- script-src:  https://cdn.amplitude.com
- worker-src:  'self' blob:

---

## Common mistakes

| Problem | Fix |
|---------|-----|
| 401 on destination | Bearer token must be `Bearer <unkey_key>` โ€” no extra spaces or quotes |
| 404 errors | Project ID in URL must match registration |
| Missing clicks / form events | Freemarker template must be present in Event Body |
| session_id = -1 | Normal during init โ€” resolves within seconds |
| No data in Autoplay | Confirm autocapture: true in initAll |
| Using wrong ingest URL | Use amplitude_ingest_url, not webhook_url |
| CSP errors | Add *.amplitude.com to connect-src, cdn.amplitude.com to script-src, blob: to worker-src |

Autoplay ร— Amplitude Integration Guide

This guide is structured for an AI agent. Steps marked ๐Ÿค– AGENT are handled autonomously. Steps marked ๐Ÿง‘ HUMAN require customer input โ€” each includes an Arcade walkthrough link.

Step 1 โ€” Understand the project

๐Ÿค– AGENT Before writing any code, read the project to determine:
  • Frontend framework โ€” Next.js, Nuxt, Angular, Vue, SvelteKit, plain React, etc.
  • Auth system โ€” what library handles login/logout and how to get the current user and session ID
  • Env var conventions โ€” how the project names and exposes environment variables (e.g. VITE_, NEXT_PUBLIC_, NG_APP_, no prefix, etc.)
  • App entry point โ€” where to initialize SDKs (e.g. main.ts, app.module.ts, _app.tsx, main.js)
  • Package manager โ€” npm, yarn, pnpm, bun
Do not write any code until this is understood. The implementation in later steps must follow the patterns already established in the project.

Step 2 โ€” Install the Amplitude SDK

๐Ÿค– AGENT Check if @amplitude/unified is already in the projectโ€™s dependencies. If not, install it using the projectโ€™s package manager:
npm install @amplitude/unified
# or yarn / pnpm / bun equivalent

Step 3 โ€” Get Amplitude credentials

๐Ÿง‘ HUMAN โ€” Amplitude API Key

Ask the customer to open their Amplitude project and copy the API Key.
๐Ÿ“บ How to find your Amplitude API Key

๐Ÿง‘ HUMAN โ€” Amplitude Project ID

Ask the customer to go to Settings โ†’ Projects โ†’ [their project] and copy the numeric Project ID.
๐Ÿ“บ How to find your Amplitude Project ID

Step 4 โ€” Register with Autoplay

๐Ÿค– AGENT Once the Project ID is known, run the registration script. Create a temporary virtual environment if needed:
python3 -m venv .venv && source .venv/bin/activate
pip install autoplay-sdk
import asyncio
from autoplay_sdk.admin import onboard_product
from autoplay_sdk.providers import AmplitudeProvider

async def main():
    result = await onboard_product(
        "<AMPLITUDE_PROJECT_ID>",
        contact_email="<customer email>",
        user_activity_provider=AmplitudeProvider(),
        print_operator_summary=True,
    )

asyncio.run(main())
The script prints three values. Store them โ€” they are needed in Steps 5 and 6:
KeyUsed for
amplitude_ingest_urlAmplitude dashboard HTTP destination URL
stream_urlIdentify endpoint called from the app
unkey_keyBearer token for both
Add these to the projectโ€™s env file using whatever naming convention the project already uses. Also add the Amplitude API Key and Project ID from Step 3. The values that must be accessible client-side (API Key, Product ID) should use the frameworkโ€™s public env prefix. The rest can stay server-side.

Step 5 โ€” Wire Amplitude into the app

๐Ÿค– AGENT Based on what was learned in Step 1, implement the following using the projectโ€™s existing patterns:

Initialize Amplitude at app startup

Call amplitude.initAll() once, as early as possible in the app lifecycle โ€” at the frameworkโ€™s equivalent of app bootstrap:
import * as amplitude from '@amplitude/unified';

amplitude.initAll('<AMPLITUDE_API_KEY>', {
  analytics: { autocapture: true },
  sessionReplay: { sampleRate: 1 },
});
autocapture: true is required โ€” it captures page views, clicks, and form interactions automatically.

Identify the user on login

Find where the project handles successful login and add the following immediately after the user object is available. Use whatever the projectโ€™s auth system provides for user ID, email, and name:
amplitude.setUserId(<user.id>);

const identifyObj = new amplitude.Identify();
identifyObj.set('product_id', '<AUTOPLAY_PRODUCT_ID>');
identifyObj.set('email', <user.email>);
identifyObj.set('name', <user.name or display name>);
amplitude.identify(identifyObj);
Then link the Amplitude session to Autoplay. The session_id comes from Amplitude, not the auth system:
const sessionId = amplitude.getSessionId();
if (sessionId) {
  // POST to your backend proxy (see below) โ€” do not call stream_url directly from the client
  fetch('<your identify proxy endpoint>', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email: <user.email>,
      session_id: String(sessionId),
      product_id: '<AUTOPLAY_PRODUCT_ID>',
    }),
  });
}

Create a server-side proxy for the identify call

The stream_url and unkey_key should not be exposed to the client. Create a backend endpoint using whatever the projectโ€™s server-side routing supports (Next.js route handler, Express endpoint, Nuxt server route, Angular API proxy, etc.) that forwards the identify payload:
POST <stream_url>/identify
Content-Type: application/json

{ email, session_id, product_id }
No auth header is needed for the identify call.

Reset on logout

Find where the project handles logout and call:
amplitude.reset();

Step 6 โ€” Create the Amplitude Event Streaming destination

๐Ÿง‘ HUMAN

The agent cannot access the Amplitude dashboard. Ask the customer to:
  1. Go to Data โ†’ Destinations โ†’ Add Destination โ†’ HTTP โ†’ Event Streaming
  2. Configure the destination:
FieldValue
Endpoint URLamplitude_ingest_url from Step 4
AuthenticationBearer Token
Tokenunkey_key from Step 4
Events to sendAll events
  1. In the Event Body section, paste this Freemarker template exactly:
<#setting number_format="0.####">
<#assign et = input.event_type!''>
<#assign ep = input.event_properties!{}>
<#assign up = input.user_properties!{}>

{
  "events": [
    {
      "event_type": "<#if et?starts_with('Viewed') || et == '[Amplitude] Page Viewed' || et == 'Page Viewed'>$pageview<#elseif et == '[Amplitude] Element Clicked' || et == '[Amplitude] Element Changed' || et?starts_with('Form')>$autocapture<#else>${et}</#if>",
      "user_id": "${input.user_id!''}",
      "device_id": "${input.device_id!''}",
      "session_id": ${input.session_id!0},
      "time": ${input.time!0},
      "user_properties": {
        <#list up?keys as k>"${k}": "${up[k]}"<#sep>, </#sep></#list>
      },
      "event_properties": {
        "[Amplitude] Page URL": "${ep['Page URL']!ep['Page Location']!ep['[Amplitude] Page URL']!ep['[Amplitude] Page Location']!''}",
        "[Amplitude] Page Title": "${ep['Page Title']!ep['[Amplitude] Page Title']!''}",
        "$event_type": "<#if et == '[Amplitude] Element Changed'>change<#elseif et?starts_with('Form Submitted')>submit<#elseif et?starts_with('Form Started')>focus<#else>click</#if>",
        "$current_url": "${ep['Page URL']!ep['Page Location']!ep['[Amplitude] Page URL']!ep['[Amplitude] Page Location']!''}",
        "$button_text": "${ep['[Amplitude] Element Text']!ep['Element Text']!ep['Page Title']!''}",
        "$elements_chain": "${ep['[Amplitude] Element Path']!ep['[Amplitude] Element Hierarchy']!ep['Element Path']!''}",
        "$element_id": "${ep['[Amplitude] Element ID']!ep['Element ID']!''}",
        "$element_tag": "${ep['[Amplitude] Element Tag']!ep['Element Tag']!''}"
      }
    }
  ]
}
๐Ÿ“บ Walkthrough: Set up Amplitude Event Streaming destination

Step 7 โ€” Verify

๐Ÿค– AGENT (customer triggers events) Ask the customer to log in and navigate around their app. Then stream live events to confirm data is flowing:
curl -N "<stream_url>" \
  -H "Authorization: Bearer <unkey_key>"
Session activity should appear within seconds.

Content Security Policy (CSP)

๐Ÿค– AGENT If the project sets a Content-Security-Policy header (common in Next.js via next.config.ts, or via middleware), the Amplitude SDK will be blocked unless the following three directives are updated:
DirectiveWhat to addWhy
connect-srchttps://*.amplitude.comSDK fetches remote config, sends events, and streams session replay data to Amplitude subdomains
script-srchttps://cdn.amplitude.comSDK dynamically loads the engagement/split script from the Amplitude CDN
worker-src'self' blob:SDK spawns a Web Worker from a blob URL for background event compression
If worker-src is not explicitly set, browsers fall back to script-src, which blocks blob workers. The SDK degrades gracefully when workers are blocked (falls back to main-thread sending), but the CSP violations will spam the console and session replay may not function correctly.

Common mistakes

ProblemFix
401 errors on destinationBearer token must be Bearer <unkey_key> โ€” no extra spaces or quotes
404 errorsProject ID in the URL must exactly match what was used at registration
Missing clicks / form eventsEvent Body Freemarker template must be present
session_id = -1Normal during Amplitude init โ€” resolves within seconds
No data in AutoplayConfirm autocapture: true is set in initAll
Using wrong ingest URLUse amplitude_ingest_url for the destination, not webhook_url
CSP errors in consoleAdd https://*.amplitude.com to connect-src, https://cdn.amplitude.com to script-src, and 'self' blob: to worker-src