Skip to main content
Ask your AI

Meta Conversions API

Server Source code Package

Sends walkerOS events from your server to the Meta Conversions API. Customer information such as email and phone number is normalized and SHA-256 hashed before the request leaves your server.

Where this fits

Meta CAPI is a server destination in the walkerOS flow:

It receives events from the collector and posts each one to the Conversions API endpoint of your Meta Pixel.

Installation

npm install @walkeros/server-destination-meta
import { startFlow } from '@walkeros/collector';
import { destinationMeta } from '@walkeros/server-destination-meta';

await startFlow({
  destinations: {
    meta: {
      code: destinationMeta,
      config: {
        settings: {
          accessToken: 'YOUR_ACCESS_TOKEN',
          pixelId: '1234567890',
        },
      },
    },
  },
});

Configuration

This destination uses the standard destination config wrapper (consent, data, env, id, ...). For the shared fields see destination configuration. Package-specific fields live under config.settings and are listed below.

Settings

PropertyTypeDescriptionMore
accessToken*stringMeta access token for Conversions API authentication
pixelId*stringMeta Pixel ID from your Facebook Business account
action_sourceSource of the event (website, app, phone_call, etc.)
doNotHashArray<string>user_data keys to send without normalizing and hashing, for values that are already hashed. Only em, ph, fn, ln, db, ge, ct, st, zp, country and external_id are hashed
test_event_codestringTest event code for debugging Meta Conversions API events
urlstringCustom URL for Meta Conversions API endpoint
user_dataRecord<string, Mapping.Value>Mapping of Meta customer information parameters to event values, applied to every event. Keys must be Meta's short names such as em and ph
* Required fields

Mapping

This package does not define custom rule-level settings. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

Examples

Form submit

A form submission is forwarded to Meta CAPI as a custom event with the event source URL.

Event
{
  "name": "form submit",
  "data": {
    "type": "newsletter"
  },
  "context": {
    "dev": [
      "test",
      1
    ]
  },
  "globals": {
    "lang": "elb"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "email": "user@example.com"
  },
  "nested": [
    {
      "entity": "child",
      "data": {
        "is": "subordinated"
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "c1d2e3f4a5b60002",
  "trigger": "test",
  "entity": "form",
  "action": "submit",
  "timestamp": 1700000901000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "browser",
    "platform": "web",
    "url": "https://example.com"
  }
}
Out
sendServer("https://graph.facebook.com/v22.0/1234567890/events", "{\"data\":[{\"event_name\":\"form submit\",\"event_id\":\"c1d2e3f4a5b60002\",\"event_time\":1700000901,\"action_source\":\"website\",\"user_data\":{},\"event_source_url\":\"https://example.com\"}]}", {
  "headers": {
    "Authorization": "Bearer s3cr3t"
  }
})

Purchase

A completed order is sent to the Meta Conversions API as a Purchase event with value, currency, and contents.

Event
{
  "name": "order complete",
  "data": {
    "id": "ORD-300",
    "total": 249.99,
    "currency": "EUR"
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "user-123",
    "device": "device-456"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "SKU-A1",
        "price": 129.99,
        "quantity": 2
      }
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "c1d2e3f4a5b60001",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000900000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "browser",
    "platform": "web",
    "url": "https://shop.example.com"
  }
}
Mapping
{
  "name": "Purchase",
  "data": {
    "map": {
      "order_id": "data.id",
      "currency": {
        "key": "data.currency",
        "value": "EUR"
      },
      "value": "data.total",
      "contents": {
        "loop": [
          "nested",
          {
            "condition": {
              "$code": "e=>\"object\"==typeof e&&null!==e&&\"entity\"in e&&\"product\"===e.entity"
            },
            "map": {
              "id": "data.id",
              "item_price": "data.price",
              "quantity": {
                "key": "data.quantity",
                "value": 1
              }
            }
          }
        ]
      },
      "num_items": {
        "fn": {
          "$code": "e=>e.nested.filter(e=>\"product\"===e.entity).length"
        }
      }
    }
  }
}
Out
sendServer("https://graph.facebook.com/v22.0/1234567890/events", "{\"data\":[{\"event_name\":\"Purchase\",\"event_id\":\"c1d2e3f4a5b60001\",\"event_time\":1700000900,\"action_source\":\"website\",\"order_id\":\"ORD-300\",\"currency\":\"EUR\",\"value\":249.99,\"contents\":[{\"id\":\"SKU-A1\",\"item_price\":129.99,\"quantity\":2}],\"num_items\":1,\"user_data\":{},\"event_source_url\":\"https://shop.example.com\"}]}", {
  "headers": {
    "Authorization": "Bearer s3cr3t"
  }
})

Purchase with fbclid

A purchase is sent to Meta CAPI with an external_id and a formatted fbc click id for ads attribution.

Event
{
  "name": "order complete",
  "data": {
    "id": "ORD-700",
    "total": 89.99,
    "currency": "USD"
  },
  "context": {
    "fbclid": [
      "abc123xyz",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "id": "cust-42"
  },
  "nested": [
    {
      "entity": "product",
      "data": {
        "id": "ers",
        "name": "Everyday Ruck Snack",
        "color": "black",
        "size": "l",
        "price": 420
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "product",
      "data": {
        "id": "cc",
        "name": "Cool Cap",
        "size": "one size",
        "price": 42
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    },
    {
      "entity": "gift",
      "data": {
        "name": "Surprise"
      },
      "context": {
        "shopping": [
          "complete",
          0
        ]
      },
      "nested": []
    }
  ],
  "consent": {
    "functional": true
  },
  "id": "c1d2e3f4a5b60003",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000902000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "browser",
    "platform": "web",
    "url": "https://shop.example.com"
  }
}
Mapping
{
  "name": "Purchase",
  "data": {
    "map": {
      "currency": {
        "key": "data.currency",
        "value": "EUR"
      },
      "value": "data.total",
      "order_id": "data.id",
      "user_data": {
        "map": {
          "external_id": "user.id",
          "fbclid": "context.fbclid.0"
        }
      }
    }
  }
}
Out
sendServer("https://graph.facebook.com/v22.0/1234567890/events", "{\"data\":[{\"event_name\":\"Purchase\",\"event_id\":\"c1d2e3f4a5b60003\",\"event_time\":1700000902,\"action_source\":\"website\",\"currency\":\"USD\",\"value\":89.99,\"order_id\":\"ORD-700\",\"user_data\":{\"external_id\":\"8a3c5a67cad508582b5edf6b8352cea3ffbad7f44812c1a736b4444c0f5746aa\",\"fbc\":\"fb.1.1700000902000.abc123xyz\"},\"event_source_url\":\"https://shop.example.com\"}]}", {
  "headers": {
    "Authorization": "Bearer s3cr3t"
  }
})

How it works

Every event becomes one POST request to {url}/{pixelId}/events, with the access token in an Authorization: Bearer header. The default url is https://graph.facebook.com/v22.0, and a trailing slash is optional. The destination does not batch events.

The server event is built from the walkerOS event:

FieldValue
event_nameThe walkerOS event name, such as order complete. Set name in a mapping rule to send a Meta event name such as Purchase.
event_idThe walkerOS event.id. If the Meta Pixel tracks the same event in the browser, give it the same ID so Meta can deduplicate the two.
event_timeThe event timestamp converted from milliseconds to seconds.
action_sourcesettings.action_source, or website if it is not set.
event_source_urlevent.source.url, added only when action_source is website.

Keys returned by data mappings are added to the top level of the server event. user_data combines settings.user_data with the user_data mapped in the destination config.data and in the rule's data. When the same key is mapped in more than one place, the rule's value wins.

If user_data contains fbclid, the destination replaces it with fbc in the format fb.1.{creationTime}.{fbclid}. The creation time is the session start in milliseconds when the collector has a session, and the event timestamp otherwise.

If test_event_code is set, it is added to the request body.

A response outside the 2xx range, a network error, or a timeout makes the push throw with the response details. The destination does not retry.

Customer information hashing

user_data keys must be Meta's parameter names from Meta's customer information parameters, such as em for email and ph for phone. fbclid is also accepted and becomes fbc. The settings schema and the TypeScript types list only these parameter names, so a key like email is rejected by this package's schema and by type checking. Any other key that reaches user_data, for example through a data mapping, is dropped before the request is sent, with a warning that lists the key names but never their values.

The destination normalizes and SHA-256 hashes these keys. Arrays are handled item by item.

KeyNormalization before hashing
emTrim and lowercase.
phKeep only digits and drop leading zeros. The destination cannot add a country code, so the mapped value must include one.
fn, lnLowercase, remove punctuation, and trim. Accented and non-Latin letters are kept.
dbOnly year-first dates are normalized: 1997-02-16 becomes 19970216. Other formats such as 02/16/1997 are only trimmed and cannot match, so map dates as YYYYMMDD. The destination logs a warning when a date is not 8 digits after normalization.
geFirst letter, lowercased (Female becomes f).
ct, stLowercase and remove everything except letters and digits. Full state names are not converted to codes.
zpLowercase and remove spaces and dashes. U.S. ZIP+4 codes such as 94035-1234 or 940351234 keep the first 5 digits.
countryTrim and lowercase. Map the ISO 3166-1 alpha-2 code, since country names are not converted.
external_idNone. The value is hashed as it is.

The other parameters, such as client_ip_address, client_user_agent, fbc, or fbp, are sent unchanged. If a value is already hashed, add its key to doNotHash and the destination sends it exactly as mapped.

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)