> Part of the walkerOS documentation. Project overview and full index: <https://www.walkeros.io/llms.txt>

# X (Twitter) Conversions API

<!-- -->

[Server](#)[ ](https://github.com/elbwalker/walkerOS/tree/main/packages/server/destinations/twitter)

<!-- -->

[Source code](https://github.com/elbwalker/walkerOS/tree/main/packages/server/destinations/twitter)[ ](https://www.npmjs.com/package/@walkeros/server-destination-twitter)

<!-- -->

[Package](https://www.npmjs.com/package/@walkeros/server-destination-twitter)

<!-- -->

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[​](#installation "Direct link to Installation")

```
npm install @walkeros/server-destination-twitter
```

* Integrated
* Bundled

```
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',
        },
      },
    },
  },
});
```

Add to your `flow.json` destinations:

```
"destinations": {
  "twitter": {
    "package": "@walkeros/server-destination-twitter",
    "import": "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"
      }
    }
  }
}
```

[See bundled mode setup](https://www.walkeros.io/docs/getting-started/modes/bundled.md) | [CLI reference](https://www.walkeros.io/docs/apps/cli.md)

## Configuration[​](#configuration "Direct link to Configuration")

This <!-- -->destination<!-- --> uses the standard <!-- -->destination<!-- --> config wrapper (consent, data, env, id, ...). For the shared fields see [destination<!-- --> configuration](https://www.walkeros.io/docs/destinations.md#configuration). Package-specific fields live under `config.settings` and are listed below.

## Settings[​](#settings "Direct link to Settings")

| Property             | Type                     | Description                                         | More |
| -------------------- | ------------------------ | --------------------------------------------------- | ---- |
| `pixelId*`           | `string`                 | X Pixel ID used in the Conversions API endpoint URL |      |
| `eventId*`           | `string`                 | Default pre-registered conversion event ID          |      |
| `consumerKey*`       | `string`                 | OAuth 1.0a API Key (Consumer Key) for X Ads API     |      |
| `consumerSecret*`    | `string`                 | OAuth 1.0a API Key Secret (Consumer Secret)         |      |
| `accessToken*`       | `string`                 | OAuth 1.0a User Access Token                        |      |
| `accessTokenSecret*` | `string`                 | OAuth 1.0a User Access Token Secret                 |      |
| `apiVersion`         | `string`                 | X Ads API version number                            |      |
| `doNotHash`          | `Array<string>`          | Array of user data fields that should not be hashed |      |
| `url`                | `string`                 | Custom base URL for the X Conversions API endpoint  |      |
| `user_data`          | `Record<string, string>` | Mapping configuration for user identifiers          |      |

\* Required fields

## Mapping[​](#mapping "Direct link to Mapping")

Per-event rules under `config.mapping`. For the standard rule fields (consent, condition, data, batch, name, policy) see [mapping](https://www.walkeros.io/docs/mapping.md).

| Property       | Type               | Description                                             | More |
| -------------- | ------------------ | ------------------------------------------------------- | ---- |
| `eventId`      | `string`           | Override the default conversion event ID for this event |      |
| `value`        | `string \| number` | Conversion monetary value (sent to X as a string)       |      |
| `currency`     | `string`           | ISO 4217 currency code                                  |      |
| `number_items` | `integer`          | Number of items in the conversion                       |      |
| `description`  | `string`           | Free-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[​](#user-identification "Direct link to 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[​](#per-event-overrides "Direct link to 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[​](#browser-deduplication "Direct link to 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.
