@apideck/agent-analytics 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +448 -0
- package/dist/adapters/posthog.cjs +31 -0
- package/dist/adapters/posthog.cjs.map +1 -0
- package/dist/adapters/posthog.d.cts +31 -0
- package/dist/adapters/posthog.d.ts +31 -0
- package/dist/adapters/posthog.js +29 -0
- package/dist/adapters/posthog.js.map +1 -0
- package/dist/adapters/webhook.cjs +24 -0
- package/dist/adapters/webhook.cjs.map +1 -0
- package/dist/adapters/webhook.d.cts +24 -0
- package/dist/adapters/webhook.d.ts +24 -0
- package/dist/adapters/webhook.js +22 -0
- package/dist/adapters/webhook.js.map +1 -0
- package/dist/index.cjs +152 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +58 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +142 -0
- package/dist/index.js.map +1 -0
- package/dist/markdown.cjs +68 -0
- package/dist/markdown.cjs.map +1 -0
- package/dist/markdown.d.cts +63 -0
- package/dist/markdown.d.ts +63 -0
- package/dist/markdown.js +64 -0
- package/dist/markdown.js.map +1 -0
- package/dist/types-DOy0kk0t.d.cts +39 -0
- package/dist/types-DOy0kk0t.d.ts +39 -0
- package/package.json +80 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Apideck
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# agent-analytics
|
|
4
|
+
|
|
5
|
+
### See the agents your JavaScript can't.
|
|
6
|
+
|
|
7
|
+
**Drop-in Next.js / Vercel middleware that tracks ClaudeBot, GPTBot, Perplexity, and 20+ AI crawlers in PostHog — or any analytics backend you already pay for.**
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@apideck/agent-analytics)
|
|
10
|
+
[](https://www.npmjs.com/package/@apideck/agent-analytics)
|
|
11
|
+
[](https://bundlephobia.com/package/@apideck/agent-analytics)
|
|
12
|
+
[](https://github.com/apideck-libraries/agent-analytics/actions)
|
|
13
|
+
[](./LICENSE)
|
|
14
|
+
[](./tsconfig.json)
|
|
15
|
+
|
|
16
|
+
[**Install**](#install) · [**Quick start**](#quick-start-60-seconds-to-your-first-event) · [**How it works**](#how-it-works) · [**Adapters**](#built-in-adapters) · [**Markdown mirror**](#advanced-markdown-mirror-for-docs-sites) · [**FAQ**](#faq)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## The problem
|
|
23
|
+
|
|
24
|
+
Client-side analytics libraries run in the browser. AI crawlers don't. That means **every time ClaudeBot, GPTBot, or Perplexity fetches a page on your site, your dashboard stays empty.**
|
|
25
|
+
|
|
26
|
+
You can't see:
|
|
27
|
+
|
|
28
|
+
- Which AI agents are reading your docs, marketing pages, or blog
|
|
29
|
+
- Which pages they actually fetch (vs. which you *think* they should)
|
|
30
|
+
- Which agent-driven referrals convert
|
|
31
|
+
- How much of your "traffic" is actually LLM training pipelines
|
|
32
|
+
|
|
33
|
+
Server logs have the data, but turning them into analytics is a pipeline project. This library is the one-line version.
|
|
34
|
+
|
|
35
|
+
## What you get
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { trackDocView, posthogAnalytics } from '@apideck/agent-analytics'
|
|
39
|
+
|
|
40
|
+
const analytics = posthogAnalytics({ apiKey: process.env.POSTHOG_KEY! })
|
|
41
|
+
|
|
42
|
+
export function middleware(req: NextRequest) {
|
|
43
|
+
void trackDocView(req, { analytics }) // ← that's the whole thing
|
|
44
|
+
return NextResponse.next()
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
One line of middleware. Fire-and-forget. Zero impact on your response latency. Events in PostHog within seconds.
|
|
49
|
+
|
|
50
|
+
<details>
|
|
51
|
+
<summary><b>What shows up in your analytics</b> (click to expand)</summary>
|
|
52
|
+
|
|
53
|
+
```jsonc
|
|
54
|
+
{
|
|
55
|
+
"event": "doc_view",
|
|
56
|
+
"distinct_id": "anon_7f3a1b2c", // hashed ip:ua, no person profile
|
|
57
|
+
"timestamp": "2026-04-19T08:30:00.000Z",
|
|
58
|
+
"properties": {
|
|
59
|
+
"$process_person_profile": false, // PostHog: don't create a person
|
|
60
|
+
"$current_url": "https://example.com/docs/intro",
|
|
61
|
+
"path": "/docs/intro",
|
|
62
|
+
"user_agent": "ClaudeBot/1.0 (+https://claude.ai/bot)",
|
|
63
|
+
"is_ai_bot": true, // trivially segmentable
|
|
64
|
+
"referer": "https://claude.ai/",
|
|
65
|
+
"source": "page-view" // whatever label you passed
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Now you can build:
|
|
71
|
+
- **AI-vs-human traffic ratio** over time
|
|
72
|
+
- **Breakdown by agent** (Claude vs ChatGPT vs Perplexity vs Google-Extended)
|
|
73
|
+
- **Top pages for agents** (what do they actually read?)
|
|
74
|
+
- **Conversion funnels** from agent referral → human visit → sign-up
|
|
75
|
+
- **Anomaly detection** when a new bot starts hammering your site
|
|
76
|
+
|
|
77
|
+
</details>
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install @apideck/agent-analytics
|
|
85
|
+
# or
|
|
86
|
+
pnpm add @apideck/agent-analytics
|
|
87
|
+
# or
|
|
88
|
+
yarn add @apideck/agent-analytics
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Zero dependencies. Runs on Node 18+, Edge, Bun, and anywhere the Web Fetch API exists.
|
|
92
|
+
|
|
93
|
+
## Quick start (60 seconds to your first event)
|
|
94
|
+
|
|
95
|
+
<table>
|
|
96
|
+
<tr>
|
|
97
|
+
<td width="33%" valign="top">
|
|
98
|
+
|
|
99
|
+
### 1. Pick an adapter
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import {
|
|
103
|
+
posthogAnalytics
|
|
104
|
+
} from '@apideck/agent-analytics'
|
|
105
|
+
|
|
106
|
+
const analytics = posthogAnalytics({
|
|
107
|
+
apiKey: process.env.POSTHOG_KEY!
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Ships with **PostHog**, **webhook**, and **custom** adapters. BYO analytics.
|
|
112
|
+
|
|
113
|
+
</td>
|
|
114
|
+
<td width="33%" valign="top">
|
|
115
|
+
|
|
116
|
+
### 2. Wire the middleware
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// middleware.ts
|
|
120
|
+
import {
|
|
121
|
+
trackDocView
|
|
122
|
+
} from '@apideck/agent-analytics'
|
|
123
|
+
|
|
124
|
+
export function middleware(req) {
|
|
125
|
+
void trackDocView(req, {
|
|
126
|
+
analytics,
|
|
127
|
+
source: 'page-view'
|
|
128
|
+
})
|
|
129
|
+
return NextResponse.next()
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Works in any middleware that hands you a `Request`.
|
|
134
|
+
|
|
135
|
+
</td>
|
|
136
|
+
<td width="33%" valign="top">
|
|
137
|
+
|
|
138
|
+
### 3. Ship it
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
vercel --prod
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Hit any page with a spoofed UA:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
curl -A "ClaudeBot/1.0" \
|
|
148
|
+
https://yoursite.com/
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Event lands in PostHog in seconds.
|
|
152
|
+
|
|
153
|
+
</td>
|
|
154
|
+
</tr>
|
|
155
|
+
</table>
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## How it works
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
Request Response (unchanged)
|
|
163
|
+
Agent ─────────────────────► middleware ───────────────────► Agent
|
|
164
|
+
│
|
|
165
|
+
│ fire-and-forget
|
|
166
|
+
│ keepalive: true
|
|
167
|
+
▼
|
|
168
|
+
┌──────────────────┐
|
|
169
|
+
│ AnalyticsAdapter │
|
|
170
|
+
│ (PostHog / │
|
|
171
|
+
│ webhook / │
|
|
172
|
+
│ custom fn) │
|
|
173
|
+
└──────────────────┘
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The middleware call:
|
|
177
|
+
|
|
178
|
+
1. **Reads UA** from `req.headers.get('user-agent')`
|
|
179
|
+
2. **Matches against** `AI_BOT_PATTERN` (ClaudeBot, GPTBot, PerplexityBot, Google-Extended, Applebot-Extended, CCBot, Bytespider, Amazonbot, Meta-ExternalAgent, MistralAI-User, Cursor, Windsurf, and more)
|
|
180
|
+
3. **Hashes `ip:ua`** with djb2 → stable anon distinct_id (same bot from same network = same visitor, no PII)
|
|
181
|
+
4. **Posts to your adapter** with `keepalive: true` so the request survives after the response returns
|
|
182
|
+
5. **Swallows errors** — a downed analytics backend never breaks your response
|
|
183
|
+
|
|
184
|
+
By default only AI bots are captured. Pass `onlyBots: false` to track every request.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Who's detected out of the box
|
|
189
|
+
|
|
190
|
+
<table>
|
|
191
|
+
<tr><th>Agent</th><th>UA signature</th><th>bot_name label</th></tr>
|
|
192
|
+
<tr><td><b>Anthropic</b></td><td><code>ClaudeBot</code>, <code>Claude-User</code>, <code>Anthropic-*</code></td><td><code>Claude</code></td></tr>
|
|
193
|
+
<tr><td><b>OpenAI</b></td><td><code>ChatGPT-User</code>, <code>GPTBot</code>, <code>OAI-SearchBot</code></td><td><code>ChatGPT</code></td></tr>
|
|
194
|
+
<tr><td><b>Perplexity</b></td><td><code>PerplexityBot</code>, <code>Perplexity-User</code></td><td><code>Perplexity</code></td></tr>
|
|
195
|
+
<tr><td><b>Google</b></td><td><code>Google-Extended</code>, <code>Googlebot</code></td><td><code>Google</code></td></tr>
|
|
196
|
+
<tr><td><b>Apple</b></td><td><code>Applebot-Extended</code>, <code>Applebot</code></td><td><code>Apple</code></td></tr>
|
|
197
|
+
<tr><td><b>Meta</b></td><td><code>Meta-ExternalAgent</code>, <code>FacebookBot</code></td><td><code>Meta</code></td></tr>
|
|
198
|
+
<tr><td><b>Amazon</b></td><td><code>Amazonbot</code></td><td><code>Amazon</code></td></tr>
|
|
199
|
+
<tr><td><b>Bytedance</b></td><td><code>Bytespider</code></td><td><code>Bytespider</code></td></tr>
|
|
200
|
+
<tr><td><b>Common Crawl</b></td><td><code>CCBot</code></td><td><code>Common Crawl</code></td></tr>
|
|
201
|
+
<tr><td><b>Mistral</b></td><td><code>MistralAI-User</code></td><td><code>Mistral</code></td></tr>
|
|
202
|
+
<tr><td><b>Cohere</b></td><td><code>cohere-ai</code></td><td><code>Cohere</code></td></tr>
|
|
203
|
+
<tr><td><b>DuckDuckGo</b></td><td><code>DuckAssistBot</code></td><td><code>DuckDuckGo</code></td></tr>
|
|
204
|
+
<tr><td><b>You.com</b></td><td><code>YouBot</code></td><td><code>You.com</code></td></tr>
|
|
205
|
+
<tr><td><b>AI2</b></td><td><code>AI2Bot</code></td><td><code>AI2</code></td></tr>
|
|
206
|
+
<tr><td><b>Diffbot</b></td><td><code>Diffbot</code></td><td><code>Diffbot</code></td></tr>
|
|
207
|
+
<tr><td><b>Coding agents</b></td><td><code>Cursor</code>, <code>Windsurf</code></td><td><code>Cursor</code> / <code>Windsurf</code></td></tr>
|
|
208
|
+
</table>
|
|
209
|
+
|
|
210
|
+
New agents appear every month. Patch releases ship as the list grows — watch the repo for updates. Raise a PR if you spot one we're missing.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Built-in adapters
|
|
215
|
+
|
|
216
|
+
### `posthogAnalytics`
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { posthogAnalytics } from '@apideck/agent-analytics'
|
|
220
|
+
|
|
221
|
+
const analytics = posthogAnalytics({
|
|
222
|
+
apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!,
|
|
223
|
+
host: 'https://eu.i.posthog.com' // optional; defaults to US cloud
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Host can be the PostHog cloud (`us.i.posthog.com`, `eu.i.posthog.com`) **or** your own reverse-proxy domain (e.g. `https://svc.yourdomain.com`) to dodge ad-blockers. Scheme is optional — both `'https://host'` and `'host'` work.
|
|
228
|
+
|
|
229
|
+
### `webhookAnalytics`
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
import { webhookAnalytics } from '@apideck/agent-analytics'
|
|
233
|
+
|
|
234
|
+
const analytics = webhookAnalytics({
|
|
235
|
+
url: 'https://collector.example.com/events',
|
|
236
|
+
headers: { Authorization: `Bearer ${process.env.TOKEN}` },
|
|
237
|
+
transform: (event) => ({ // optional: reshape for your backend
|
|
238
|
+
type: event.event,
|
|
239
|
+
user: event.distinctId,
|
|
240
|
+
...event.properties
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `customAnalytics`
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { customAnalytics } from '@apideck/agent-analytics'
|
|
249
|
+
import { Mixpanel } from 'mixpanel'
|
|
250
|
+
|
|
251
|
+
const mp = Mixpanel.init(process.env.MIXPANEL_TOKEN!)
|
|
252
|
+
|
|
253
|
+
const analytics = customAnalytics((event) => {
|
|
254
|
+
mp.track(event.event, { distinct_id: event.distinctId, ...event.properties })
|
|
255
|
+
})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Any `{ capture(event): Promise<void> | void }` object is a valid adapter. Compose multiple by fanning out in a custom callback.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Advanced: Markdown mirror for docs sites
|
|
263
|
+
|
|
264
|
+
Content-heavy sites should serve **clean Markdown** when an agent asks for it — that's what makes your docs actually useful to coding agents, not just indexable. The `/markdown` subpath exports the helpers that power [developers.apideck.com](https://developers.apideck.com)'s agent-readiness stack:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import {
|
|
268
|
+
markdownServeDecision, // decide if this request should get Markdown
|
|
269
|
+
markdownHeaders, // Content-Type, Content-Signal, x-markdown-tokens
|
|
270
|
+
synthesizeMarkdownPointer // fallback for URLs without a mirror
|
|
271
|
+
} from '@apideck/agent-analytics/markdown'
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Three triggers, one decision helper:
|
|
275
|
+
|
|
276
|
+
| Trigger | Example | `reason` |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| AI-bot UA on any URL | `curl -A ClaudeBot /docs/intro` | `ua-rewrite` |
|
|
279
|
+
| `.md` suffix | `curl /docs/intro.md` | `md-suffix` |
|
|
280
|
+
| `Accept: text/markdown` header | `curl -H "Accept: text/markdown" /docs/intro` | `accept-header` |
|
|
281
|
+
|
|
282
|
+
Full middleware example: [`README.md → Markdown mirror helpers`](./README.md#markdown-mirror-helpers) section, or copy from [the reference implementation](https://github.com/apideck-io/developer-docs/blob/main/src/middleware.ts).
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Compared to…
|
|
287
|
+
|
|
288
|
+
<table>
|
|
289
|
+
<tr>
|
|
290
|
+
<th></th>
|
|
291
|
+
<th align="center">@apideck/agent-analytics</th>
|
|
292
|
+
<th align="center">DIY middleware</th>
|
|
293
|
+
<th align="center">Dark Visitors SaaS</th>
|
|
294
|
+
<th align="center">Cloudflare AI Labyrinth</th>
|
|
295
|
+
</tr>
|
|
296
|
+
<tr>
|
|
297
|
+
<td><b>Tracks agents in your analytics</b></td>
|
|
298
|
+
<td align="center">✓</td>
|
|
299
|
+
<td align="center">✓ (after N hours of glue code)</td>
|
|
300
|
+
<td align="center">✓ (external dashboard)</td>
|
|
301
|
+
<td align="center">✗ (it blocks them instead)</td>
|
|
302
|
+
</tr>
|
|
303
|
+
<tr>
|
|
304
|
+
<td><b>Reuses your analytics backend</b></td>
|
|
305
|
+
<td align="center">✓ PostHog / webhook / any</td>
|
|
306
|
+
<td align="center">✓</td>
|
|
307
|
+
<td align="center">✗ (their dashboard)</td>
|
|
308
|
+
<td align="center">✗</td>
|
|
309
|
+
</tr>
|
|
310
|
+
<tr>
|
|
311
|
+
<td><b>Zero runtime dependencies</b></td>
|
|
312
|
+
<td align="center">✓</td>
|
|
313
|
+
<td align="center">✓</td>
|
|
314
|
+
<td align="center">✗ (SaaS)</td>
|
|
315
|
+
<td align="center">✗ (Cloudflare)</td>
|
|
316
|
+
</tr>
|
|
317
|
+
<tr>
|
|
318
|
+
<td><b>Ships maintained UA list</b></td>
|
|
319
|
+
<td align="center">✓</td>
|
|
320
|
+
<td align="center">✗</td>
|
|
321
|
+
<td align="center">✓</td>
|
|
322
|
+
<td align="center">✓</td>
|
|
323
|
+
</tr>
|
|
324
|
+
<tr>
|
|
325
|
+
<td><b>Markdown-mirror helpers</b></td>
|
|
326
|
+
<td align="center">✓</td>
|
|
327
|
+
<td align="center">✗</td>
|
|
328
|
+
<td align="center">✗</td>
|
|
329
|
+
<td align="center">✗</td>
|
|
330
|
+
</tr>
|
|
331
|
+
<tr>
|
|
332
|
+
<td><b>Monthly cost</b></td>
|
|
333
|
+
<td align="center">$0</td>
|
|
334
|
+
<td align="center">$0 + engineering time</td>
|
|
335
|
+
<td align="center">$$$</td>
|
|
336
|
+
<td align="center">Requires CF plan</td>
|
|
337
|
+
</tr>
|
|
338
|
+
</table>
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## FAQ
|
|
343
|
+
|
|
344
|
+
<details>
|
|
345
|
+
<summary><b>Will this slow down my middleware?</b></summary>
|
|
346
|
+
|
|
347
|
+
No. `trackDocView` returns a promise you don't await, and the underlying `fetch` uses `keepalive: true` — the browser / runtime guarantees the request completes after your response returns. Your critical path is: `req.headers.get('user-agent')` + a regex test + a `void fetch(...)`. Sub-millisecond.
|
|
348
|
+
|
|
349
|
+
</details>
|
|
350
|
+
|
|
351
|
+
<details>
|
|
352
|
+
<summary><b>What if my analytics backend is down?</b></summary>
|
|
353
|
+
|
|
354
|
+
The adapter call is wrapped in try/catch — `trackDocView` never throws, even if PostHog / your webhook / your custom callback crashes. You lose the event, not the response.
|
|
355
|
+
|
|
356
|
+
</details>
|
|
357
|
+
|
|
358
|
+
<details>
|
|
359
|
+
<summary><b>Does this create PostHog person profiles for every bot?</b></summary>
|
|
360
|
+
|
|
361
|
+
No. The event includes `$process_person_profile: false`, which tells PostHog to skip profile creation. Distinct IDs are djb2 hashes of `ip:ua`, so same-bot-same-network collapses into one anonymous visitor for journey analysis, but no "person" row gets created.
|
|
362
|
+
|
|
363
|
+
</details>
|
|
364
|
+
|
|
365
|
+
<details>
|
|
366
|
+
<summary><b>How do I detect a bot I added to the UA list in my own code?</b></summary>
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { isAiBot, parseBotName } from '@apideck/agent-analytics'
|
|
370
|
+
|
|
371
|
+
if (isAiBot(req.headers.get('user-agent'))) {
|
|
372
|
+
// serve Markdown, skip personalisation, add rate limits, etc.
|
|
373
|
+
}
|
|
374
|
+
parseBotName('ClaudeBot/1.0') // → 'Claude'
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
</details>
|
|
378
|
+
|
|
379
|
+
<details>
|
|
380
|
+
<summary><b>Can I use this outside Next.js?</b></summary>
|
|
381
|
+
|
|
382
|
+
Yes. The primary API takes a standard Web Fetch `Request` object. Works in Hono, Bun, Cloudflare Workers, Deno Deploy, Node 18+ HTTP handlers — anywhere you can get a `Request`.
|
|
383
|
+
|
|
384
|
+
</details>
|
|
385
|
+
|
|
386
|
+
<details>
|
|
387
|
+
<summary><b>Why not just enable PostHog's bot filtering?</b></summary>
|
|
388
|
+
|
|
389
|
+
PostHog's bot filter excludes bots from your metrics. This library does the opposite: it makes bots *visible* so you can analyse them deliberately. Complementary — segment by `is_ai_bot` to split the populations.
|
|
390
|
+
|
|
391
|
+
</details>
|
|
392
|
+
|
|
393
|
+
<details>
|
|
394
|
+
<summary><b>Is the UA list going to go stale?</b></summary>
|
|
395
|
+
|
|
396
|
+
AI crawlers keep appearing. We publish patch releases whenever the list changes — `npm update @apideck/agent-analytics` picks them up. If you spot a missing agent, send a PR with a link to the bot's official docs; merges ship the same day.
|
|
397
|
+
|
|
398
|
+
</details>
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Who uses this
|
|
403
|
+
|
|
404
|
+
- **[developers.apideck.com](https://developers.apideck.com)** — extracted from and battle-tested on the Apideck developer documentation site
|
|
405
|
+
- *Your company here — send a PR*
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Roadmap
|
|
410
|
+
|
|
411
|
+
- [ ] Runtime UA list fetching (opt-in) so patches land without a dependency bump
|
|
412
|
+
- [ ] First-class Mixpanel, Amplitude, Segment adapters
|
|
413
|
+
- [ ] Vercel Marketplace one-click install
|
|
414
|
+
- [ ] Pre-built PostHog dashboards (JSON export) for AI-vs-human, agent leaderboard, top-pages-per-agent
|
|
415
|
+
- [ ] `createMarkdownMiddleware()` — a batteries-included Next.js middleware for the full agent-readiness stack
|
|
416
|
+
|
|
417
|
+
File a [feature request](https://github.com/apideck-libraries/agent-analytics/issues/new) if something's missing from your setup.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Contributing
|
|
422
|
+
|
|
423
|
+
PRs welcome — especially new UA signatures, adapters, and docs.
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
git clone https://github.com/apideck-libraries/agent-analytics
|
|
427
|
+
cd agent-analytics
|
|
428
|
+
npm install
|
|
429
|
+
npm test
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Credits
|
|
433
|
+
|
|
434
|
+
Built on learnings from:
|
|
435
|
+
|
|
436
|
+
- [Agentic Engine Optimization](https://addyosmani.com/blog/agentic-engine-optimization/) by Addy Osmani — the case for making sites agent-ready
|
|
437
|
+
- [contentsignals.org](https://contentsignals.org) — the Content-Signal spec
|
|
438
|
+
- [darkvisitors.com](https://darkvisitors.com) — maintained catalogue of AI user-agents we cross-reference
|
|
439
|
+
|
|
440
|
+
## License
|
|
441
|
+
|
|
442
|
+
[MIT](./LICENSE) © [Apideck](https://apideck.com)
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
<div align="center">
|
|
447
|
+
<sub>Built by <a href="https://apideck.com">Apideck</a> — the unified API platform for integrations.</sub>
|
|
448
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/posthog.ts
|
|
4
|
+
function posthogAnalytics(config) {
|
|
5
|
+
const hostRaw = config.host ?? "https://us.i.posthog.com";
|
|
6
|
+
const base = (/^https?:\/\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\/$/, "");
|
|
7
|
+
const path = (config.path ?? "/i/v0/e/").replace(/^(?!\/)/, "/");
|
|
8
|
+
const endpoint = `${base}${path}`;
|
|
9
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
10
|
+
return {
|
|
11
|
+
async capture(event) {
|
|
12
|
+
const payload = {
|
|
13
|
+
api_key: config.apiKey,
|
|
14
|
+
event: event.event,
|
|
15
|
+
distinct_id: event.distinctId,
|
|
16
|
+
timestamp: event.timestamp,
|
|
17
|
+
properties: event.properties
|
|
18
|
+
};
|
|
19
|
+
await fetchImpl(endpoint, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify(payload),
|
|
23
|
+
keepalive: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.posthogAnalytics = posthogAnalytics;
|
|
30
|
+
//# sourceMappingURL=posthog.cjs.map
|
|
31
|
+
//# sourceMappingURL=posthog.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/posthog.ts"],"names":[],"mappings":";;;AA6BO,SAAS,iBAAiB,MAAA,EAAgD;AAC/E,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,IAAQ,0BAAA;AAC/B,EAAA,MAAM,IAAA,GAAA,CAAQ,cAAA,CAAe,IAAA,CAAK,OAAO,CAAA,GAAI,OAAA,GAAU,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC9F,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,IAAQ,UAAA,EAAY,OAAA,CAAQ,WAAW,GAAG,CAAA;AAC/D,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,IAAI,CAAA,EAAG,IAAI,CAAA,CAAA;AAC/B,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,KAAA;AAEtC,EAAA,OAAO;AAAA,IACL,MAAM,QAAQ,KAAA,EAAoC;AAChD,MAAA,MAAM,OAAA,GAAU;AAAA,QACd,SAAS,MAAA,CAAO,MAAA;AAAA,QAChB,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,aAAa,KAAA,CAAM,UAAA;AAAA,QACnB,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,YAAY,KAAA,CAAM;AAAA,OACpB;AACA,MAAA,MAAM,UAAU,QAAA,EAAU;AAAA,QACxB,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AAAA,QAC5B,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH;AAAA,GACF;AACF","file":"posthog.cjs","sourcesContent":["import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true\n })\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { A as AnalyticsAdapter } from '../types-DOy0kk0t.cjs';
|
|
2
|
+
|
|
3
|
+
interface PostHogAdapterConfig {
|
|
4
|
+
/** PostHog project API key (the public one used by the JS SDK). */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/**
|
|
7
|
+
* PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.
|
|
8
|
+
* Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy
|
|
9
|
+
* domain (e.g. `https://svc.example.com`).
|
|
10
|
+
*/
|
|
11
|
+
host?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Path on the host that accepts single-event captures. Defaults to
|
|
14
|
+
* `/i/v0/e/` which is PostHog's current endpoint for this.
|
|
15
|
+
*/
|
|
16
|
+
path?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Override the `fetch` implementation (useful for tests or custom runtimes
|
|
19
|
+
* that need a pinned fetch).
|
|
20
|
+
*/
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adapter that posts each event to the PostHog capture endpoint. Uses
|
|
25
|
+
* `keepalive: true` so the request survives after a serverless response
|
|
26
|
+
* returns — events aren't guaranteed (fire-and-forget), but that's the
|
|
27
|
+
* trade we want to keep the hot path fast.
|
|
28
|
+
*/
|
|
29
|
+
declare function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter;
|
|
30
|
+
|
|
31
|
+
export { type PostHogAdapterConfig, posthogAnalytics };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { A as AnalyticsAdapter } from '../types-DOy0kk0t.js';
|
|
2
|
+
|
|
3
|
+
interface PostHogAdapterConfig {
|
|
4
|
+
/** PostHog project API key (the public one used by the JS SDK). */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/**
|
|
7
|
+
* PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.
|
|
8
|
+
* Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy
|
|
9
|
+
* domain (e.g. `https://svc.example.com`).
|
|
10
|
+
*/
|
|
11
|
+
host?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Path on the host that accepts single-event captures. Defaults to
|
|
14
|
+
* `/i/v0/e/` which is PostHog's current endpoint for this.
|
|
15
|
+
*/
|
|
16
|
+
path?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Override the `fetch` implementation (useful for tests or custom runtimes
|
|
19
|
+
* that need a pinned fetch).
|
|
20
|
+
*/
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adapter that posts each event to the PostHog capture endpoint. Uses
|
|
25
|
+
* `keepalive: true` so the request survives after a serverless response
|
|
26
|
+
* returns — events aren't guaranteed (fire-and-forget), but that's the
|
|
27
|
+
* trade we want to keep the hot path fast.
|
|
28
|
+
*/
|
|
29
|
+
declare function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter;
|
|
30
|
+
|
|
31
|
+
export { type PostHogAdapterConfig, posthogAnalytics };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/adapters/posthog.ts
|
|
2
|
+
function posthogAnalytics(config) {
|
|
3
|
+
const hostRaw = config.host ?? "https://us.i.posthog.com";
|
|
4
|
+
const base = (/^https?:\/\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\/$/, "");
|
|
5
|
+
const path = (config.path ?? "/i/v0/e/").replace(/^(?!\/)/, "/");
|
|
6
|
+
const endpoint = `${base}${path}`;
|
|
7
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
8
|
+
return {
|
|
9
|
+
async capture(event) {
|
|
10
|
+
const payload = {
|
|
11
|
+
api_key: config.apiKey,
|
|
12
|
+
event: event.event,
|
|
13
|
+
distinct_id: event.distinctId,
|
|
14
|
+
timestamp: event.timestamp,
|
|
15
|
+
properties: event.properties
|
|
16
|
+
};
|
|
17
|
+
await fetchImpl(endpoint, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify(payload),
|
|
21
|
+
keepalive: true
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { posthogAnalytics };
|
|
28
|
+
//# sourceMappingURL=posthog.js.map
|
|
29
|
+
//# sourceMappingURL=posthog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/posthog.ts"],"names":[],"mappings":";AA6BO,SAAS,iBAAiB,MAAA,EAAgD;AAC/E,EAAA,MAAM,OAAA,GAAU,OAAO,IAAA,IAAQ,0BAAA;AAC/B,EAAA,MAAM,IAAA,GAAA,CAAQ,cAAA,CAAe,IAAA,CAAK,OAAO,CAAA,GAAI,OAAA,GAAU,CAAA,QAAA,EAAW,OAAO,CAAA,CAAA,EAAI,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC9F,EAAA,MAAM,QAAQ,MAAA,CAAO,IAAA,IAAQ,UAAA,EAAY,OAAA,CAAQ,WAAW,GAAG,CAAA;AAC/D,EAAA,MAAM,QAAA,GAAW,CAAA,EAAG,IAAI,CAAA,EAAG,IAAI,CAAA,CAAA;AAC/B,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,KAAA;AAEtC,EAAA,OAAO;AAAA,IACL,MAAM,QAAQ,KAAA,EAAoC;AAChD,MAAA,MAAM,OAAA,GAAU;AAAA,QACd,SAAS,MAAA,CAAO,MAAA;AAAA,QAChB,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,aAAa,KAAA,CAAM,UAAA;AAAA,QACnB,WAAW,KAAA,CAAM,SAAA;AAAA,QACjB,YAAY,KAAA,CAAM;AAAA,OACpB;AACA,MAAA,MAAM,UAAU,QAAA,EAAU;AAAA,QACxB,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,QAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AAAA,QAC5B,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH;AAAA,GACF;AACF","file":"posthog.js","sourcesContent":["import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\nexport interface PostHogAdapterConfig {\n /** PostHog project API key (the public one used by the JS SDK). */\n apiKey: string\n /**\n * PostHog host, with or without scheme. Defaults to `https://us.i.posthog.com`.\n * Use `https://eu.i.posthog.com` for EU cloud, or your own reverse-proxy\n * domain (e.g. `https://svc.example.com`).\n */\n host?: string\n /**\n * Path on the host that accepts single-event captures. Defaults to\n * `/i/v0/e/` which is PostHog's current endpoint for this.\n */\n path?: string\n /**\n * Override the `fetch` implementation (useful for tests or custom runtimes\n * that need a pinned fetch).\n */\n fetchImpl?: typeof fetch\n}\n\n/**\n * Adapter that posts each event to the PostHog capture endpoint. Uses\n * `keepalive: true` so the request survives after a serverless response\n * returns — events aren't guaranteed (fire-and-forget), but that's the\n * trade we want to keep the hot path fast.\n */\nexport function posthogAnalytics(config: PostHogAdapterConfig): AnalyticsAdapter {\n const hostRaw = config.host ?? 'https://us.i.posthog.com'\n const base = (/^https?:\\/\\//.test(hostRaw) ? hostRaw : `https://${hostRaw}`).replace(/\\/$/, '')\n const path = (config.path ?? '/i/v0/e/').replace(/^(?!\\/)/, '/')\n const endpoint = `${base}${path}`\n const fetchImpl = config.fetchImpl ?? fetch\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n const payload = {\n api_key: config.apiKey,\n event: event.event,\n distinct_id: event.distinctId,\n timestamp: event.timestamp,\n properties: event.properties\n }\n await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n keepalive: true\n })\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/adapters/webhook.ts
|
|
4
|
+
function webhookAnalytics(config) {
|
|
5
|
+
const fetchImpl = config.fetchImpl ?? fetch;
|
|
6
|
+
const transform = config.transform ?? ((e) => e);
|
|
7
|
+
return {
|
|
8
|
+
async capture(event) {
|
|
9
|
+
await fetchImpl(config.url, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
...config.headers ?? {}
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify(transform(event)),
|
|
16
|
+
keepalive: true
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
exports.webhookAnalytics = webhookAnalytics;
|
|
23
|
+
//# sourceMappingURL=webhook.cjs.map
|
|
24
|
+
//# sourceMappingURL=webhook.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/webhook.ts"],"names":[],"mappings":";;;AAsBO,SAAS,iBAAiB,MAAA,EAAgD;AAC/E,EAAA,MAAM,SAAA,GAAY,OAAO,SAAA,IAAa,KAAA;AACtC,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAA,KAAc,CAAC,CAAA,KAA6B,CAAA,CAAA;AAErE,EAAA,OAAO;AAAA,IACL,MAAM,QAAQ,KAAA,EAAoC;AAChD,MAAA,MAAM,SAAA,CAAU,OAAO,GAAA,EAAK;AAAA,QAC1B,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB,kBAAA;AAAA,UAChB,GAAI,MAAA,CAAO,OAAA,IAAW;AAAC,SACzB;AAAA,QACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,QACrC,SAAA,EAAW;AAAA,OACZ,CAAA;AAAA,IACH;AAAA,GACF;AACF","file":"webhook.cjs","sourcesContent":["import type { AnalyticsAdapter, CaptureEvent } from '../types.js'\n\nexport interface WebhookAdapterConfig {\n /** Destination URL that receives a POST for each event. */\n url: string\n /** Extra headers merged onto the POST (useful for shared-secret auth). */\n headers?: Record<string, string>\n /**\n * Transform the event into the exact JSON body the destination expects.\n * Defaults to sending the {@link CaptureEvent} as-is.\n */\n transform?: (event: CaptureEvent) => unknown\n /** Override the `fetch` implementation. */\n fetchImpl?: typeof fetch\n}\n\n/**\n * Adapter that POSTs each event to an arbitrary webhook URL. Keeps the\n * library analytics-backend-agnostic — use this when PostHog isn't your\n * analytics of record, or when you want to multiplex events through your\n * own ingestion layer.\n */\nexport function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter {\n const fetchImpl = config.fetchImpl ?? fetch\n const transform = config.transform ?? ((e: CaptureEvent): unknown => e)\n\n return {\n async capture(event: CaptureEvent): Promise<void> {\n await fetchImpl(config.url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.headers ?? {})\n },\n body: JSON.stringify(transform(event)),\n keepalive: true\n })\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { C as CaptureEvent, A as AnalyticsAdapter } from '../types-DOy0kk0t.cjs';
|
|
2
|
+
|
|
3
|
+
interface WebhookAdapterConfig {
|
|
4
|
+
/** Destination URL that receives a POST for each event. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Extra headers merged onto the POST (useful for shared-secret auth). */
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Transform the event into the exact JSON body the destination expects.
|
|
10
|
+
* Defaults to sending the {@link CaptureEvent} as-is.
|
|
11
|
+
*/
|
|
12
|
+
transform?: (event: CaptureEvent) => unknown;
|
|
13
|
+
/** Override the `fetch` implementation. */
|
|
14
|
+
fetchImpl?: typeof fetch;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Adapter that POSTs each event to an arbitrary webhook URL. Keeps the
|
|
18
|
+
* library analytics-backend-agnostic — use this when PostHog isn't your
|
|
19
|
+
* analytics of record, or when you want to multiplex events through your
|
|
20
|
+
* own ingestion layer.
|
|
21
|
+
*/
|
|
22
|
+
declare function webhookAnalytics(config: WebhookAdapterConfig): AnalyticsAdapter;
|
|
23
|
+
|
|
24
|
+
export { type WebhookAdapterConfig, webhookAnalytics };
|