Bing (Microsoft Ads) Conversions
This guide covers how to submit offline conversions to Microsoft Advertising (Bing) through the TNT Growth API.
Authentication
All requests require an x-api-key header with your client-specific API key provided by TNT Growth.
-H "x-api-key: YOUR_API_KEY"
Submitting a Conversion
Endpoint
POST /api/bing/conversions/offline/add
Request Body Schema
| Field | Type | Required | Description |
|---|---|---|---|
customerAccountId | string | Yes | Your Microsoft Ads customer account ID (provided during onboarding) |
conversionName | string | Yes | The name of the offline conversion goal in Microsoft Ads |
microsoftClickId | string | Conditional | Microsoft Click ID (msclkid) captured from the ad click. Optional when you send email or phoneNumber for enhanced conversions. |
conversionTime | number | Yes | Unix timestamp (seconds) of when the conversion occurred |
conversionValue | number | No | Monetary value of the conversion |
email | string | Conditional | Customer email address (hashed before sending to Microsoft). Enables enhanced conversions and can substitute for microsoftClickId. |
phoneNumber | string | Conditional | Customer phone number (hashed before sending to Microsoft). Enables enhanced conversions and can substitute for microsoftClickId. |
ingestionSource | string | No | Origin of the event (e.g. pixel, conversion-api) |
tntId | string | No | The attribution handle from the pixel. A resolved session fills microsoftClickId and any missing UTM fields |
utmSource | string | No | UTM source parameter (e.g. bing) — stored for attribution reporting, and also decides delivery when it names a different paid platform. See When we withhold a conversion |
utmMedium | string | No | UTM medium parameter (e.g. cpc) — stored for attribution reporting |
utmCampaign | string | No | UTM campaign name — stored for attribution reporting |
utmTerm | string | No | UTM search keyword — stored for attribution reporting |
utmContent | string | No | UTM content parameter (ad/creative variant) — stored for attribution reporting |
Provide at least one of microsoftClickId, email, or phoneNumber. A request with none of these is rejected with 400 Bad Request — Microsoft has nothing to attribute the conversion to.
Example Request
curl -X POST https://flex.tntgrowth.io/api/bing/conversions/offline/add \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"customerAccountId": "B012345678",
"conversionName": "Offline Purchase",
"microsoftClickId": "abc123def456ghi789",
"conversionTime": 1778043599,
"conversionValue": 150,
"email": "customer@example.com",
"phoneNumber": "+15551234567",
"tntId": "9f2b7c1e-4d3a-4f8b-9c2e-1a5d7e0b4c63",
"utmSource": "bing",
"utmMedium": "cpc",
"utmCampaign": "brand-search-q2",
"utmTerm": "best treatment center",
"utmContent": "expanded-text-ad-variant-b"
}'
Example Response
{
"message": "Offline conversion sent to bing Example Client B012345678 Offline Purchase",
"data": {
"submissionId": "8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c",
"duplicate": false
}
}
When a conversion is dedup-skipped (the same event was submitted within the last 24 hours), submissionId is null and duplicate is true.
Checking Submission Status
After submitting a conversion, use the submission ID to check processing status.
Endpoint
GET /api/conversions/submissions/:submissionId
Example Request
curl https://flex.tntgrowth.io/api/conversions/submissions/8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c \
-H "x-api-key: YOUR_API_KEY"
Example Response
{
"id": "8f3c2d10-7e6b-4a51-9d8c-3b2e1f0a9b8c",
"source": "BING",
"externalAccountId": "B012345678",
"expectedCount": 1,
"createdAt": "2026-05-11T12:00:00.000Z",
"scheduledFor": null,
"counts": {
"success": 1,
"failed": 0
},
"status": "complete"
}
Status Values
| Status | Description |
|---|---|
pending | Conversion is queued and waiting to be processed |
pending_delivery | Submission is scheduled for future delivery. Currently only emitted by Google Ads ingest paths; Bing submissions never enter this state. |
complete | Every conversion reached a final outcome and none was rejected. Usually means all were delivered — but a withheld conversion also finishes here, so compare counts.success with expectedCount if you need delivery confirmed |
complete_with_errors | Processing finished but some conversions failed — use GET /api/conversions/bing/failed?submissionId=<id> to fetch the actual error messages |
stalled | Processing has not progressed — contact support |
Withheld Conversions
A conversion is withheld when its attribution says another paid channel served the click — a utmSource naming
a different platform, with no Microsoft Ads click ID beside it. Uploading it would train Microsoft Ads's bidding on traffic
Microsoft Ads never sent, so we hold it back instead. The full rules, and the utm_source values that count as a claim,
are in When we withhold a conversion from a platform.
When TNT Growth has set your account to send Bing only click-attributed conversions, a conversion with no
microsoftClickId is withheld as well, including an email- or phone-only match, whatever its utmSource. Those
entries carry "reason": "click_id_required" and no claimedPlatform.
This is not an error. A withheld pair appears in counts.withheld, is excluded from counts.success, and
never counts as failed — failed means the platform rejected it. A submission whose only non-delivered pairs
were withheld finishes as complete, so compare counts.success with expectedCount if you need delivery
confirmed.
{
"expectedCount": 2,
"counts": {
"success": 1,
"failed": 0,
"withheld": 1
},
"withheld": [
{
"reason": "cross_channel",
"claimedPlatform": "google",
"count": 1
}
],
"status": "complete"
}
Both counts.withheld and the withheld array are present only when something was withheld, so a submission
with nothing withheld returns exactly the shape it always has. Each entry carries reason (cross_channel,
click_id_required, or other for a reason not documented here), claimedPlatform on cross_channel entries
only (google, meta, bing, tiktok, linkedin, reddit or openai), and count; the entries sum to
counts.withheld. Nothing is discarded — the payload is kept, so a withheld conversion can still be delivered
later if your tagging or the rules change. Re-sending the same payload will be withheld again; send the Microsoft
Ads click ID and it is delivered, whatever utmSource says.
The scheduledFor field is always null for Bing submissions and is included only for schema parity with Google Ads, which uses it for deferred delivery.
Inspecting Failed Conversions In Your Batch
When a submission ends with complete_with_errors, the submission status endpoint surfaces only the count of failed rows. Fetch the actual error messages with:
GET /api/conversions/bing/failed?submissionId=<submissionId>
The response payload mirrors the Google Ads failed-conversion shape — see Google Ads → Inspecting Failed Conversions In Your Batch.
Enhanced Conversions
Microsoft treats the msclkid as optional when you supply a hashed email or phone number — the same idea as Google Enhanced Conversions. This lets you attribute conversions where the click ID wasn't captured (e.g. a phone lead), using the customer's identifiers instead.
How attribution is chosen per conversion:
| You send | Attributed by |
|---|---|
microsoftClickId only | Click ID |
email / phoneNumber only (no microsoftClickId) | Enhanced (hashed identifiers) |
| Both | Click ID, with hashed identifiers attached to improve match rate |
email and phoneNumber are always hashed on our servers before they leave (SHA-256 email, E.164 phone) — raw values are never sent to Microsoft.
Hashed identifiers only match if enhanced conversions are enabled on the offline conversion goal in your Microsoft Advertising account. After enabling it, allow a couple of hours before relying on enhanced-only attribution. See Microsoft's OfflineConversion reference.
Example: enhanced conversion (no click ID)
curl -X POST https://flex.tntgrowth.io/api/bing/conversions/offline/add \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"customerAccountId": "B012345678",
"conversionName": "Offline Purchase",
"conversionTime": 1778043599,
"conversionValue": 150,
"email": "customer@example.com",
"phoneNumber": "+15551234567"
}'
Best Practices
- Include
microsoftClickIdwhen you have it — themsclkidgives Microsoft the most reliable attribution to the originating click. When it isn't available, sendemail/phoneNumberso the conversion can still be attributed via enhanced conversions - Use accurate timestamps —
conversionTimeshould reflect when the conversion actually occurred; Microsoft rejects conversions older than its lookback window - Size the goal's conversion window for your sales cycle — Microsoft also rejects a conversion whose click happened longer before
conversionTimethan the goal's conversion window (set per goal in Microsoft Ads, up to 90 days). These rejections are final and are not retried, so widen the window before sending conversions that close weeks after the click - Match
conversionNameexactly to the offline conversion goal configured in Microsoft Ads - Send
emailand/orphoneNumberto enable enhanced conversion matching (and to raise match rate even when amicrosoftClickIdis present) - Poll with a delay — wait 30-60 seconds after submission before checking status
- Send
tntIdwhen your backend has it — the attribution handle recovers the click ID and UTMs captured on the visitor's original ad click, which is often the only thing that attributes an offline conversion at all