Meta Conversions API (CAPI)
Datafly Signal delivers events to Meta (Facebook) server-to-server using the Conversions API (CAPI). This provides reliable event delivery that is not affected by ad blockers or browser tracking prevention.
API Endpoint
POST https://graph.facebook.com/v18.0/{pixel_id}/eventsEvents are sent as a JSON array in the data field, with the access_token as a query parameter or in the request body.
Configuration
| Field | Required | Description |
|---|---|---|
pixel_id | Yes | Your Meta Pixel ID (numeric). Found in Meta Events Manager. |
access_token | Yes | System user access token with ads_management permission. Generate in Meta Business Settings > System Users. |
test_event_code | No | Test event code for validation (e.g. TEST12345). Events sent with this code appear in the Events Manager Test Events tab. |
Management UI Setup
- Go to Integrations > Add Integration > Meta Conversions API.
- Enter your
pixel_idandaccess_token. - Select consent categories (typically
advertisingormarketing). - Click Save.
Management API Setup
curl -X POST http://localhost:8084/v1/admin/integrations \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_id": "src_abc123",
"vendor": "meta_capi",
"name": "Meta CAPI Production",
"enabled": true,
"config": {
"pixel_id": "1234567890",
"access_token": "your_access_token"
},
"consent_categories": ["advertising"]
}'Identity Signals
Meta uses multiple identity signals to match server events to user profiles. The more signals you provide, the higher your Event Match Quality (EMQ) score.
Automatic Signals
| Signal | Field | Description |
|---|---|---|
fbp | user_data.fbp | Facebook browser ID. Datafly Signal self-generates this in the format fb.1.{timestamp}.{random} and stores it as a first-party cookie. |
fbc | user_data.fbc | Facebook click ID. Automatically extracted from the fbclid URL parameter and formatted as fb.1.{timestamp}.{fbclid}. |
external_id | user_data.external_id | Set to the Datafly anonymous_id (hashed). Provides a stable cross-session identifier. |
client_ip_address | user_data.client_ip_address | Visitor’s IP address, forwarded from the original request. |
client_user_agent | user_data.client_user_agent | Visitor’s User-Agent string, forwarded from the original request. |
User-Provided Signals (Hashed)
When a user is identified via _df.identify() with traits, the following fields are SHA-256 hashed before sending to Meta:
| Signal | Hashing | Description |
|---|---|---|
em (email) | SHA-256, lowercase, trimmed | User’s email address |
ph (phone) | SHA-256, digits only, with country code | User’s phone number |
fn (first name) | SHA-256, lowercase, trimmed | First name |
ln (last name) | SHA-256, lowercase, trimmed | Last name |
ct (city) | SHA-256, lowercase, no spaces | City |
st (state) | SHA-256, lowercase, 2-letter code | State/region |
zp (zip) | SHA-256, trimmed | Postal/zip code |
country | SHA-256, lowercase, 2-letter ISO code | Country |
All PII hashing is performed server-side by the Delivery Worker before the data leaves your infrastructure. Raw PII is never sent to Meta.
Hashing Example
Input email: John.Doe@Example.com
Normalisation steps:
- Trim whitespace:
John.Doe@Example.com - Lowercase:
john.doe@example.com - SHA-256 hash:
836f82db99121b3481011f16b49dfa5fbc714a0d1b1b9f784a1ebbbf5b39577f
Event Mapping
| Datafly Event | Meta Event | Notes |
|---|---|---|
page (page view) | PageView | Sent for every page view |
Order Completed / Product Purchased | Purchase | Requires value, currency |
Product Added | AddToCart | Includes content_ids, content_type |
Checkout Started | InitiateCheckout | Includes value, currency, num_items |
Product Viewed | ViewContent | Includes content_ids, content_type |
Lead Generated | Lead | Lead form submission |
Signed Up | CompleteRegistration | User registration |
Products Searched | Search | Includes search_string |
Product Added to Wishlist | AddToWishlist | Includes content_ids |
| Custom events | Passed through | Sent as custom event name |
Example: Purchase Event
Datafly.js call:
_df.track("Order Completed", {
order_id: "ORD-001",
total: 129.99,
currency: "USD",
products: [
{ product_id: "SKU-A", name: "Widget", price: 49.99, quantity: 2 },
{ product_id: "SKU-B", name: "Gadget", price: 30.01, quantity: 1 }
]
});Meta CAPI payload sent by Datafly:
{
"data": [
{
"event_name": "Purchase",
"event_time": 1706540000,
"event_id": "evt_abc123def456",
"event_source_url": "https://example.com/checkout/confirmation",
"action_source": "website",
"user_data": {
"client_ip_address": "203.0.113.50",
"client_user_agent": "Mozilla/5.0 ...",
"fbp": "fb.1.1706540000.1234567890",
"fbc": "fb.1.1706539000.AbCdEfGhIjKl",
"external_id": "a1b2c3d4e5f6..."
},
"custom_data": {
"currency": "USD",
"value": 129.99,
"order_id": "ORD-001",
"content_ids": ["SKU-A", "SKU-B"],
"content_type": "product",
"contents": [
{ "id": "SKU-A", "quantity": 2, "item_price": 49.99 },
{ "id": "SKU-B", "quantity": 1, "item_price": 30.01 }
],
"num_items": 3
}
}
]
}Action Source
The action_source field tells Meta where the event originated. For all browser-originated events collected by Datafly.js, this is always set to website.
The action_source must be website for events that originate from a user browsing your site. Using incorrect values will cause Meta to reject the event or reduce match quality.
Event Match Quality (EMQ)
Event Match Quality is Meta’s score (out of 10) indicating how well your server events can be matched to Meta user profiles. A higher EMQ means better ad optimisation and attribution.
How to Improve EMQ
- Send
fbpandfbc: Datafly Signal handles this automatically. Thefbpcookie is self-generated andfbcis extracted from thefbclidURL parameter. - Include user data: Pass email and phone via
_df.identify()when users log in or submit forms. These are hashed before delivery. - Send IP and User-Agent: Datafly Signal forwards these automatically from the original browser request.
- Use
external_id: Enabled by default — the hashedanonymous_idis sent asexternal_id.
A typical Datafly Signal integration achieves an EMQ of 6-8 with automatic signals alone, and 8-10 when user-provided data (email, phone) is available.
Deduplication
If you are running both Datafly Signal server-side delivery and the Meta Pixel client-side (during a migration period, for example), you must deduplicate events to prevent double-counting.
Datafly Signal includes a unique event_id with every event. To deduplicate:
- Pass the same
event_idto both the client-side Meta Pixel and Datafly.js. - Meta will automatically deduplicate events with matching
event_nameandevent_idreceived within a 48-hour window.
// Generate a shared event ID
const eventId = crypto.randomUUID();
// Send via Meta Pixel (client-side)
fbq('track', 'Purchase', { value: 129.99, currency: 'USD' }, { eventID: eventId });
// Send via Datafly.js (server-side)
_df.track("Order Completed", {
event_id: eventId,
total: 129.99,
currency: "USD"
});Deduplication only works when the event_name and event_id match exactly. If you are migrating from client-side to server-side, plan to remove the client-side Meta Pixel once server-side delivery is validated.
Test Events
Use the test_event_code configuration to send events to Meta’s test events tool without affecting production data:
{
"config": {
"pixel_id": "1234567890",
"access_token": "your_access_token",
"test_event_code": "TEST12345"
}
}Test events appear in Meta Events Manager > Test Events. The test event code is found in the Test Events tab of your Pixel settings.
Remove the test_event_code before going to production. Events sent with a test code are not used for ad optimisation or reporting.
API Response
A successful response from Meta:
{
"events_received": 1,
"messages": [],
"fbtrace_id": "AbCdEfGhIjKlMnOpQrStUv"
}An error response:
{
"error": {
"message": "Invalid parameter",
"type": "OAuthException",
"code": 100,
"error_subcode": 2804003,
"fbtrace_id": "AbCdEfGhIjKlMnOpQrStUv"
}
}Rate Limits
Meta’s Conversions API has the following limits:
| Setting | Default |
|---|---|
rate_limit_rps | 200 |
rate_limit_burst | 400 |
Meta allows up to 1,000 events per API call (batched) and generally supports high throughput. The default limits are conservative and suitable for most deployments.
If you send more than 1,000 events per second sustained, consider increasing rate_limit_rps or contact your Meta representative for guidance.