Webclat / Signals
Signals  /  QA  /  CAPI-03

What exact user_data formatting does Meta CAPI require for event_time and IP/user-agent to be accepted?

Answer in brief

event_time must be a Unix timestamp in seconds - not milliseconds - representing when the real action happened, not when your server got around to sending it, and it must fall within roughly the last 7 days, never in the future. client_ip_address and client_user_agent must be sent as raw, unhashed strings exactly as captured at the moment of the original action, while identity fields inside user_data (email, phone, external_id, and the rest) must be lowercased, trimmed, and SHA-256 hashed before sending.

Why this happens

Meta's Conversions API deliberately mixes hashed and unhashed fields in the same user_data object, which is the single most common source of degraded or rejected matching. Hashing an IP address, which should stay in plaintext, or sending a raw unhashed email, which must be hashed, both pass basic JSON validation but fail Meta's matching logic - sometimes with no hard error at all.

event_time goes wrong in three specific, recurring ways: sending milliseconds instead of seconds (off by a factor of 1000, which usually resolves to a date decades away and gets rejected outright), sending the time your backend processed the request instead of the time the user action happened (which silently degrades matching even though the request itself succeeds), or a queued pipeline that delays sending long enough to fall outside the accepted window.

Fix it

  1. Capture event_time at the moment the action happens, not when a backend job later processes it - for a purchase, that is checkout completion time, not whenever an async worker picks the order up from a queue.
  2. Convert to Unix seconds explicitly (Math.floor(Date.now() / 1000) in JavaScript, int(time.time()) in Python) - never pass a millisecond timestamp or an ISO string directly.
  3. For identity fields (em, ph, external_id, fn, ln, ge, db, ct, st, zp, country): lowercase and trim whitespace first, then SHA-256 hash each field individually - Meta will not hash plaintext for you, and a plaintext or malformed hash simply fails to match.
  4. For client_ip_address and client_user_agent: send the exact raw values captured server-side from the request that corresponds to the user's action - do not hash, truncate, or normalize either field.
  5. Behind a load balancer or reverse proxy, read the real client IP from X-Forwarded-For (or your provider's equivalent header) rather than the proxy's own address - sending your server's IP as client_ip_address quietly degrades match quality without producing an error.
  6. Include fbp and fbc cookie values when available, captured from the browser at the same time as the corresponding Pixel event - they meaningfully help matching and cost nothing to add correctly.

How to verify it worked

  • Send one test event through Meta's Conversions API Test Events tool (using a test_event_code) and check the Server Events tab - a correctly formatted event_time renders as a normal recent date; a milliseconds-instead-of-seconds bug typically shows as a wildly wrong date or an outright rejection.
  • In the Pixel's Events Manager diagnostics, check the Parameters breakdown for the event - Meta explicitly flags fields it could not use for matching (for example "Email - Invalid format" or "IP Address - Not provided"), the fastest way to catch a hashing/plaintext mix-up.
  • Compare the Event Match Quality score before and after a formatting fix on a controlled test batch of otherwise identical traffic - an improved score confirms the fix changed what Meta could actually use, not just what your own validation accepted.

Want This Checked On Your Own Setup?

Send us the platform and the symptom and we will tell you whether this is the fix, or whether something else in your pipeline is masquerading as it.

Talk To The Practice