@g2crowd/buyer-intent-provider-sdk
v0.4.1
Published
Buyer intent tracking SDK with pageview defaults
Keywords
Readme
Buyer Intent JS SDK
Buyer intent tracking for partner sites. Drop custom HTML elements into any page — no framework required. Events are sent via sendBeacon for reliable delivery during navigation.
npm install @g2crowd/buyer-intent-provider-sdkQuick Start
Import the SDK, wrap your page in a session, and you're tracking:
<script type="module">
import '@g2crowd/buyer-intent-provider-sdk';
</script>
<buyer-intent-session
origin="yoursite.com"
activity-endpoint="/api/activity/events"
>
<buyer-intent-view tag="products.show">
<buyer-intent-subject product-id="123"></buyer-intent-subject>
<h1>Acme CRM</h1>
<p>Product details here.</p>
<buyer-intent-click event-name="/leads/create">
<button>Get a Demo</button>
</buyer-intent-click>
</buyer-intent-view>
</buyer-intent-session>That's it. When the page loads, a $view event fires with product_ids: [123] and tag: "products.show". When the user clicks "Get a Demo", a /leads/create event fires. Both events inherit the session's origin and activity-endpoint automatically.
Elements
<buyer-intent-session>
Wraps a page (or your entire app) and provides config to all elements inside it. Does not fire any events itself.
| Attribute | Description |
| -------------------- | -------------------------------------------- |
| origin | Partner hostname |
| activity-endpoint | URL to POST events to |
| user-type | Visitor role (default: "standard") |
| distinct-id | Override the auto-generated visitor ID |
| User Type | Meaning |
| ---------------- | ------------------------------------------------- |
| guest | Logged-out visitor |
| standard | Logged-in user (default) |
| vendor-admin | Vendor role viewing their own content |
| observer | Internal employee / superuser |
<buyer-intent-session origin="yoursite.com" activity-endpoint="/api/activity/events">
<!-- everything inside inherits origin and endpoint -->
</buyer-intent-session><buyer-intent-view>
Fires a $view event when the element connects to the DOM. One event per page navigation.
| Attribute | Description |
| ----------------- | ------------------------------------------- |
| tag | View type identifier (optional) |
| source-location | Controller/action string (optional) |
| product-id | Single product ID (simple pages) |
| category-id | Single category ID (simple pages) |
For pages with one product, you can put the ID directly on the view:
<buyer-intent-view tag="products.show" product-id="123">
<h1>Acme CRM</h1>
</buyer-intent-view>For pages with multiple products, use <buyer-intent-subject> children instead (see below).
<buyer-intent-subject>
Declares that a product or category is a subject of the current view. Nest any number of these inside a <buyer-intent-view> — their IDs accumulate into the view's event automatically. Does not render anything visible.
| Attribute | Description |
| -------------- | ---------------------------------------------------- |
| product-id | Single product ID |
| product-ids | Multiple product IDs (JSON array or comma-separated) |
| category-id | Single category ID |
| category-ids | Multiple category IDs (JSON array or comma-separated)|
A comparison page that references two products:
<buyer-intent-view tag="comparisons.show">
<buyer-intent-subject product-id="101"></buyer-intent-subject>
<buyer-intent-subject product-id="201"></buyer-intent-subject>
<h1>Acme CRM vs. Rival CRM</h1>
</buyer-intent-view>The resulting event has product_ids: [101, 201]. If the view also has an inline product-id, they merge together without duplicates.
Subjects can live anywhere inside the view — they don't need to be direct children. This makes them easy to place next to the content they describe:
<buyer-intent-view tag="categories.show">
<buyer-intent-subject category-id="45"></buyer-intent-subject>
<h1>CRM Software</h1>
<div class="product-grid">
<div class="product-card">
<buyer-intent-subject product-id="101"></buyer-intent-subject>
<h2>Acme CRM</h2>
</div>
<div class="product-card">
<buyer-intent-subject product-id="201"></buyer-intent-subject>
<h2>Rival CRM</h2>
</div>
</div>
</buyer-intent-view>This fires one event: category_ids: [45], product_ids: [101, 201], tag: "categories.show".
<buyer-intent-click>
Fires an event when the user clicks anything inside it.
| Attribute | Description |
| ------------ | ------------------------ |
| event-name | Click event type |
<buyer-intent-click event-name="/leads/create">
<button>Get a Demo</button>
</buyer-intent-click>A click element inside a view automatically inherits the view's tag, product IDs, and category IDs. A click inside a session inherits the session's config. You don't need to repeat anything.
Full Example
Putting it all together — a product page with a session, view, subjects, and two click actions:
<script type="module">
import '@g2crowd/buyer-intent-provider-sdk';
</script>
<buyer-intent-session
origin="yoursite.com"
activity-endpoint="/api/activity/events"
user-type="standard"
>
<buyer-intent-view
tag="products.show"
source-location="ProductsController#show"
>
<buyer-intent-subject product-id="123"></buyer-intent-subject>
<buyer-intent-subject category-id="45"></buyer-intent-subject>
<h1>Acme CRM</h1>
<p>The best CRM for small teams.</p>
<buyer-intent-click event-name="/leads/create">
<button>Get a Demo</button>
</buyer-intent-click>
<buyer-intent-click event-name="/ad/clicked">
<a href="https://acme.example.com">Visit Acme</a>
</buyer-intent-click>
</buyer-intent-view>
</buyer-intent-session>React Support
Import from /react and use the same element hierarchy with camelCase props:
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
export default function ProductPage() {
return (
<BuyerIntent.Session origin="yoursite.com" activityEndpoint="/api/activity/events">
<BuyerIntent.View tag="products.show" sourceLocation="ProductsController#show">
<BuyerIntent.Subject productId={123} />
<BuyerIntent.Subject categoryId={45} />
<h1>Acme CRM</h1>
<BuyerIntent.Click eventName="/leads/create">
<button>Get a Demo</button>
</BuyerIntent.Click>
</BuyerIntent.View>
</BuyerIntent.Session>
);
}BuyerIntent.Session, BuyerIntent.View, BuyerIntent.Subject, and BuyerIntent.Click map 1:1 to the HTML elements described above. The session is a good fit for a layout component or _app wrapper so it's set once for every page.
Syntactic Sugar
For common page types, pre-built components hardcode the tag or event-name so you don't have to:
Views
| Component | Tag | Required Prop |
| ----------------- | ----------------------- | ------------- |
| ProfileView | products.show | productId |
| PricingView | products.pricing | productId |
| CompetitorsView | products.competitors | productId |
| CategoryView | categories.show | categoryId |
| CompareView | comparisons.show | productIds |
| WriteReviewView | reviewers.take_survey | productId |
Clicks
| Component | Event Name | Required Prop |
| ----------------- | --------------- | ------------- |
| AdClick | /ad/clicked | productId |
| LeadCreateClick | /leads/create | productId |
import { BuyerIntent } from '@g2crowd/buyer-intent-provider-sdk/react';
<BuyerIntent.Session origin="yoursite.com" activityEndpoint="/api/activity/events">
<BuyerIntent.ProfileView productId={123}>
<h1>Acme CRM</h1>
<BuyerIntent.LeadCreateClick productId={123}>
<button>Get a Demo</button>
</BuyerIntent.LeadCreateClick>
</BuyerIntent.ProfileView>
</BuyerIntent.Session>These components still accept origin, activityEndpoint, sourceLocation, and context props for cases where you aren't using a session wrapper.
Backend
Next.js Route Handler
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
const POST = createNextRouteHandler({
kafkaBrokers: [process.env.KAFKA_BROKER || 'localhost:9092'],
partnerId: process.env.PARTNER_ID,
});
export { POST };Standalone Server
PARTNER_ID=partner-a KAFKA_BROKERS=broker1:9092,broker2:9092 \
buyer-intent-serverEnvironment variables: PORT (default 3000), PARTNER_ID, KAFKA_BROKERS (comma-separated), TOPIC_PREFIX, KAFKA_TOPIC, KAFKA_CLIENT_ID, USE_DEV_LOGGER=true.
Custom Kafka Producer
import { Kafka } from 'kafkajs';
import { createNextRouteHandler } from '@g2crowd/buyer-intent-provider-sdk/server';
const kafka = new Kafka({
clientId: 'partner-app',
brokers: (process.env.KAFKA_BROKERS || '').split(','),
});
const POST = createNextRouteHandler({
producer: kafka.producer({ allowAutoTopicCreation: false }),
partnerId: process.env.PARTNER_ID,
});
export { POST };Dev Adapter (No Kafka)
import {
createNextRouteHandler,
createDevLoggerProducer,
} from '@g2crowd/buyer-intent-provider-sdk/server';
const POST = createNextRouteHandler({
producer: createDevLoggerProducer(),
partnerId: 'dev',
});
export { POST };Event Payload
The client sends a beacon to the activity endpoint. The server handler wraps it into a composite event and writes it to Kafka.
Client → Server (sendBeacon body)
{
"name": "$view",
"properties": {
"product_ids": [123],
"category_ids": [45],
"tag": "products.show",
"url": "https://yoursite.com/products/acme-crm",
"user_type": "standard",
"distinct_id": "visitor-uuid",
"origin": "yoursite.com",
"source_location": "ProductsController#show",
"context": {}
},
"visit": {
"properties": {
"landing_page": "https://yoursite.com/products/acme-crm",
"referrer": "https://google.com/",
"user_agent": "Mozilla/5.0 ...",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "spring-2026",
"utm_term": "crm+software",
"utm_content": "hero-cta"
}
}
}Server → Kafka (composite event)
The server enriches the client payload with server-side tokens, timestamps, and IP before writing to Kafka. This is the shape written to the intent_events_{partnerId} topic:
{
"event": {
"id": "e0c1f2a3-...",
"name": "$view",
"time": "2026-02-17T14:20:00.000Z",
"properties": {
"product_ids": [123],
"category_ids": [45],
"tag": "products.show",
"url": "https://yoursite.com/products/acme-crm",
"user_type": "standard",
"distinct_id": "visitor-uuid",
"origin": "yoursite.com",
"source_location": "ProductsController#show",
"context": {}
}
},
"visit": {
"visit_token": "a1b2c3d4-...",
"visitor_token": "f5e6d7c8-...",
"started_at": "2026-02-17T14:20:00.000Z",
"created_at": "2026-02-17T14:20:00.000Z",
"properties": {
"landing_page": "https://yoursite.com/products/acme-crm",
"referrer": "https://google.com/",
"user_agent": "Mozilla/5.0 ...",
"ip": "203.0.113.42",
"utm_source": "newsletter",
"utm_medium": "email",
"utm_campaign": "spring-2026",
"utm_term": "crm+software",
"utm_content": "hero-cta"
}
}
}The server sets visit_token and visitor_token as httpOnly cookies so repeat visits from the same browser share stable tokens.
