@riculum/meta-conversions-api
v0.1.2
Published
Type-safe Meta (Facebook) Conversions API client for Node/Next.js. Build and send server-side events to the Graph API with dedupe support.
Maintainers
Readme
@riculum/meta-conversions-api
Type-safe Meta (Facebook) Conversions API client for Node/Next.js.
Build and send server-side events to the Graph API (/{pixel_id}/events) with dedupe support.
- TypeScript types for standard events (Purchase, AddToCart, ViewContent, ...)
- Minimal API:
buildEvent(),sendEvents(),sha256()andcreateEventId() - No dependencies (Node 18+ for native
fetch) - Works great with Next.js Route Handlers / API routes
Install
pnpm add @riculum/meta-conversions-api
# npm i @riculum/meta-conversions-apiYou also need a Pixel ID and a Meta access token (server-side secret).
Quick start (Next.js Route Handler)
// app/api/meta-capi/route.ts
import { NextRequest, NextResponse } from "next/server";
import { buildEvent, sendEvents, sha256, createEventId } from "@riculum/meta-conversions-api";
const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;
export async function POST(req: NextRequest) {
const { order, testEventCode } = await req.json();
const eventId = createEventId("purchase");
const evt = buildEvent({
name: "Purchase",
eventId,
eventSourceUrl: order?.url ?? undefined,
user: {
em: await sha256(order?.email),
client_ip_address: req.headers.get("x-forwarded-for")?.split(",")[0] ?? undefined,
client_user_agent: req.headers.get("user-agent") ?? undefined,
},
data: {
value: order.total,
currency: order.currency,
contents: order.items.map((i: any) => ({ id: i.id, quantity: i.qty, item_price: i.price })),
num_items: order.items.reduce((a: number, i: any) => a + i.qty, 0),
},
});
const res = await sendEvents({
pixelId: PIXEL_ID,
accessToken: ACCESS_TOKEN,
events: [evt],
testEventCode, // optional: Events Manager → Test Events code
graphVersion: "v19.0",
debug: true,
});
return NextResponse.json(res);
}AddToCart example (server)
import { buildEvent, sendEvents } from "@riculum/meta-conversions-api";
const evt = buildEvent({
name: "AddToCart",
eventId: "cart_123",
eventSourceUrl: "https://example.com/product/sku_123",
data: {
value: 29.9,
currency: "EUR",
content_ids: ["sku_123"],
content_type: "product",
},
});
await sendEvents({ pixelId, accessToken, events: [evt] });Testing with Test Events (test_event_code) and debug
To see your server events in Events Manager → Test Events, include your pixel’s test_event_code in sendEvents().
Turn on debug: true to log both the request body and the Graph API response on the server.
- Copy the code from Events Manager → Pixel → Test Events (looks like
TEST1234...). - Add an env var (e.g.,
META_TEST_EVENT_CODE) and pass it tosendEvents.
// app/api/meta-capi/route.ts
import { buildEvent, sendEvents, sha256 } from "@riculum/meta-conversions-api";
const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;
const TEST_EVENT_CODE = process.env.META_TEST_EVENT_CODE; // ← from Events Manager while testing
export async function POST(req: Request) {
const body = await req.json();
const evt = buildEvent({
name: "AddToCart",
eventId: `cart_${Date.now()}`,
eventSourceUrl: body.url,
user: {
client_ip_address: req.headers.get("x-forwarded-for")?.split(",")[0] ?? undefined,
client_user_agent: req.headers.get("user-agent") ?? undefined,
em: await sha256(body.email) // only if consented
},
data: {
value: body.value,
currency: body.currency,
content_ids: [body.productId],
content_type: "product"
}
});
const res = await sendEvents({
pixelId: PIXEL_ID,
accessToken: ACCESS_TOKEN,
events: [evt],
testEventCode: TEST_EVENT_CODE, // ← makes the event appear in Test Events
debug: true // ← logs request & response server-side
});
return new Response(JSON.stringify(res), { status: res.ok ? 200 : 400 });
}Note: Remove testEventCode and set debug: false in production traffic.
API surface
/** API surface (documentation only)
buildEvent<E extends FBStandardEvent>(opts: {
name: E;
actionSource?: ActionSource; // default "website"
eventId?: string;
eventSourceUrl?: string;
user?: UserData; // hash PII with sha256()
data?: FBEventMap[E]; // per-event params
eventTime?: number; // unix seconds
}): GraphEvent<E>;
sendEvents(opts: {
pixelId: string;
accessToken: string;
events: GraphEvent[];
testEventCode?: string;
graphVersion?: string; // default "v19.0"
debug?: boolean;
}): Promise<{ ok: boolean; status: number; body: any }>
sha256(input?: string): Promise<string | undefined>
createEventId(prefix?: string): string
*/Types included: FBStandardEvent, FBEventMap, UserData, ContentItem, ActionSource.
Best practices
- Hash PII (
email,phone, names, address fields) withsha256()server-side. - Use a shared
event_idto dedupe browser (fbq) and server (CAPI) events. - Send
value+currencyfor commerce events (AddToCart,InitiateCheckout,Purchase, ...). - Respect consent—only send PII when you have the user’s permission.
- Debug in Events Manager → Test Events with a
test_event_code.
License
MIT © Riculum
