Skip to main content

X (Twitter) Conversions API

Server Source code Package Beta

Server-side event delivery to X's (Twitter's) Conversions API for enhanced ad attribution and privacy-compliant conversion tracking.

Where this fits

X Conversions API is a server destination in the walkerOS flow:

Sends conversion events server-side to X's Conversions API, bypassing browser limitations for improved data quality. Deduplicates with the browser-side X Pixel via `conversion_id`.

Installation

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

await startFlow({
destinations: {
  twitter: {
    code: destinationTwitter,
    config: {
      settings: {
        pixelId: 'YOUR_PIXEL_ID',
        eventId: 'tw-xxxxx-xxxxx',
        consumerKey: 'YOUR_CONSUMER_KEY',
        consumerSecret: 'YOUR_CONSUMER_SECRET',
        accessToken: 'YOUR_ACCESS_TOKEN',
        accessTokenSecret: 'YOUR_ACCESS_TOKEN_SECRET',
      },
    },
  },
},
});

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
pixelId*stringX Pixel ID used in the Conversions API endpoint URL
eventId*stringDefault pre-registered conversion event ID
consumerKey*stringOAuth 1.0a API Key (Consumer Key) for X Ads API
consumerSecret*stringOAuth 1.0a API Key Secret (Consumer Secret)
accessToken*stringOAuth 1.0a User Access Token
accessTokenSecret*stringOAuth 1.0a User Access Token Secret
apiVersionstringX Ads API version number
doNotHashArray<string>Array of user data fields that should not be hashed
urlstringCustom base URL for the X Conversions API endpoint
user_dataRecord<string, string>Mapping configuration for user identifiers
* Required fields

Mapping

Per-event rules under config.mapping. For the standard rule fields (consent, condition, data, batch, name, policy) see mapping.

PropertyTypeDescriptionMore
eventIdstringOverride the default conversion event ID for this event
valuestring | numberConversion monetary value (sent to X as a string)
currencystringISO 4217 currency code
number_itemsintegerNumber of items in the conversion
descriptionstringFree-text description of the conversion

Examples

Lead

A form submission is sent to X as a conversion with the SHA-256 hashed email as the identifier.

Event
{
  "name": "form submit",
  "data": {
    "string": "foo",
    "number": 1,
    "boolean": true,
    "array": [
      0,
      "text",
      false
    ]
  },
  "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": "ev-1700000901000",
  "trigger": "test",
  "entity": "form",
  "action": "submit",
  "timestamp": 1700000901000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Out
sendServer("https://ads-api.x.com/12/measurement/conversions/o8z6j", "{\"conversions\":[{\"conversion_time\":\"2023-11-14T22:28:21.000Z\",\"event_id\":\"tw-o8z6j-o8z21\",\"identifiers\":[{\"hashed_email\":\"b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514\"}],\"conversion_id\":\"ev-1700000901000\"}]}", {
  "headers": {
    "Authorization": "<OAUTH_SIGNATURE>",
    "Content-Type": "application/json"
  }
})

Purchase

A completed order is sent to the X (Twitter) Conversions API with conversion value and the hashed email.

Event
{
  "name": "order complete",
  "data": {
    "total": 249.99,
    "currency": "EUR"
  },
  "context": {
    "shopping": [
      "complete",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "email": "jane@example.com"
  },
  "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": "ev-1700000900000",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000900000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Mapping
{
  "settings": {
    "value": "data.total",
    "currency": {
      "key": "data.currency",
      "value": "EUR"
    }
  }
}
Out
sendServer("https://ads-api.x.com/12/measurement/conversions/o8z6j", "{\"conversions\":[{\"conversion_time\":\"2023-11-14T22:28:20.000Z\",\"event_id\":\"tw-o8z6j-o8z21\",\"identifiers\":[{\"hashed_email\":\"8c87b489ce35cf2e2f39f80e282cb2e804932a56a213983eeeb428407d43b52d\"}],\"conversion_id\":\"ev-1700000900000\",\"value\":\"249.99\"}]}", {
  "headers": {
    "Authorization": "<OAUTH_SIGNATURE>",
    "Content-Type": "application/json"
  }
})

Purchase with twclid

A purchase is sent to X with both the hashed email and the twclid click id for ads attribution.

Event
{
  "name": "order complete",
  "data": {
    "total": 89.99,
    "currency": "USD"
  },
  "context": {
    "twclid": [
      "23opevjt88psuo13lu8d020qkn",
      0
    ]
  },
  "globals": {
    "pagegroup": "shop"
  },
  "custom": {
    "completely": "random"
  },
  "user": {
    "email": "buyer@co.com"
  },
  "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": "ev-1700000902000",
  "trigger": "load",
  "entity": "order",
  "action": "complete",
  "timestamp": 1700000902000,
  "timing": 3.14,
  "source": {
    "count": 1,
    "trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
    "type": "express",
    "platform": "server"
  }
}
Mapping
{
  "settings": {
    "value": "data.total",
    "currency": {
      "key": "data.currency",
      "value": "USD"
    }
  },
  "data": {
    "map": {
      "user_data": {
        "map": {
          "twclid": "context.twclid"
        }
      }
    }
  }
}
Out
sendServer("https://ads-api.x.com/12/measurement/conversions/o8z6j", "{\"conversions\":[{\"conversion_time\":\"2023-11-14T22:28:22.000Z\",\"event_id\":\"tw-o8z6j-o8z21\",\"identifiers\":[{\"hashed_email\":\"484c39bfb51212665d9673805c112b5ba04cbf0460b6d3f00bcdc18b92afed66\"},{\"twclid\":\"23opevjt88psuo13lu8d020qkn\"}],\"conversion_id\":\"ev-1700000902000\",\"value\":\"89.99\"}]}", {
  "headers": {
    "Authorization": "<OAUTH_SIGNATURE>",
    "Content-Type": "application/json"
  }
})

User identification

The destination builds an identifiers array. Each identifier is a separate single-key object in the payload. At least one primary identifier is required. Events without one are silently skipped.

Primary identifiers:

  • hashed_email: resolved from event.user.email or a user_data.email mapping. The email is normalized (trimmed, lowercased) and SHA-256 hashed before sending. Set doNotHash: ['email'] if you are pre-hashing upstream.
  • hashed_phone_number: resolved from event.user.phone or a user_data.phone mapping. SHA-256 hashed. Use doNotHash: ['phone'] to skip.
  • twclid: X click ID (pass-through, not hashed). Typically forwarded from the browser session:
"settings": {
"pixelId": "$env.TWITTER_PIXEL_ID",
"eventId": "$env.TWITTER_EVENT_ID",
"user_data": {
  "twclid": "context.twclid"
}
}

Secondary identifiers (optional): ip_address, user_agent, pass-through.

Per-event overrides

The default eventId from settings applies to all events. Override it per event via mapping to route different events to different pre-registered conversion events:

"mapping": {
"order": {
  "complete": {
    "settings": {
      "eventId": { "value": "tw-o8z6j-purchase01" },
      "value": "data.total",
      "currency": { "key": "data.currency", "value": "EUR" },
      "number_items": "data.count"
    }
  }
}
}

Browser deduplication

When running both the browser-side X Pixel and this server destination, the walkerOS event id is sent as conversion_id on both sides. X deduplicates conversions that share the same conversion_id and conversion event, so only one conversion is counted.

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