Test modes
Two settings decide whether a conversion really reaches a platform:
- the destination mode (per project and provider):
live,testordry_run; - the deployment ceiling
X3_CONVERSIONS_MODE– the effective mode is the lower of the two (dry_run<test<live).
X3_ENV | X3_CONVERSIONS_MODE unset | Effective ceiling |
|---|---|---|
production | live | as configured per destination |
development, test | dry_run | nothing is sent unless you set the ceiling to test |
X3_CONVERSIONS_DISABLED=1 forces dry_run everywhere (kill switch). live is only possible with
X3_ENV=production.
What each mode does
| Mode | Meta | Google Ads API | Google feed | TikTok | |
|---|---|---|---|---|---|
live | sent (a testEventCode, if set, still routes to Test events) | sent | sent | listed (feed) ¹ | sent |
test | sent with the testEventCode; without one dry_run ("test mode without testEventCode") | no test mode – dry_run unless the base URL is a local mock | validateOnly: true | listed (feed) ¹ | sent with the testEventCode |
dry_run | payload built, PII-free summary logged | same | same | listed (feed) ¹ | same |
¹ The feed is pull-based: rows are listed in every mode, and Google only imports them once you create the HTTPS schedule. Conversions of test sessions are never listed.
In test, conversions from test sessions are sent too (they are skipped in live), so you can test
with your own visits marked as test traffic.
Local provider mocks
Outside live, base URLs can point to mocks so the full pipeline – payload, response handling,
retries – runs locally:
X3_CONVERSIONS_MODE=test
X3_META_BASE_URL=http://127.0.0.1:4315
X3_LINKEDIN_BASE_URL=http://127.0.0.1:4316
X3_GOOGLE_ADS_BASE_URL=http://127.0.0.1:4317
X3_TIKTOK_BASE_URL=http://127.0.0.1:4318The overrides are ignored in live. The API's integration tests use exactly this setup.
Test the destination
curl -s -X POST https://<api-host>/v1/projects/<project>/destinations/meta/test \
-H "Authorization: Bearer $X3_SECRET_KEY"returns {provider, ok, status, detail, test}. The synthetic event never counts in reports.
From the onboarding and Claude
The onboarding's step Conversions senden has a "Test-Event senden" button per configured
destination (POST /v1/projects/{project}/setup/test-conversion with {provider, testEventCode?},
project admins). It runs the same destination test in the provider's test mode – Meta and TikTok
need a test event code (Events Manager → Test events; the stored testEventCode is used when you
pass none), Google Ads validates without uploading, LinkedIn checks token and conversion rule –,
shows the provider's answer and stores the result, so the step turns green. The MCP tool
conversions_send_test does the same (it needs project:admin, so the onboarding's Claude Code key
asks you to click the button instead). At most 6 tests per minute and project. A destination left
in test mode keeps the warning mode_test until you
switch it live.
Conversions from test visits (?x3_test=1) are stored with isTest and skipped by providers in
live mode – see Test visits and conversions.
Dry runs of single conversions
conversions.create with "dryRun": true (REST, server SDK) or MCP conversions_mark with
dry_run: true resolves the session and returns what would be stored ({"result": "dry_run", "wouldStore": …})
without storing or sending anything – useful to check matching before marking a WhatsApp lead.