
An ad platform buys more of whatever you tell it to count. If you tell it to count form fills, it spends your budget chasing form fills. The CRM already knows which of those form fills closed and which went nowhere. Until that knowledge makes it back to the platform, the budget keeps buying more of the same mix, including the part of it that was never going to buy anything.
The chain between the CRM and Microsoft Ads offline conversions is the money problem this post is about. It examines what stood between them on one lead generation account, and why an upload that reports success is not evidence that anything counted. Before the outcome data can even be sent, the campaigns themselves need to be running with their tracking intact, a separate step we covered in a post on importing Google Ads campaigns into Microsoft without losing tracking.
The vendor publishes integration docs for three platforms
HubSpot publishes documentation for creating and syncing ad conversion events with Google Ads, Meta and LinkedIn. There is no equivalent article for Microsoft. HubSpot does list Microsoft among the ad accounts a customer can connect, and its Microsoft-specific pages are in beta. Those pages cover connecting the account, campaign tracking and reporting. The English versions of those Microsoft-specific pages are password protected, so what we read were the public translations. Those translations do not mention conversion events.
On Microsoft’s own Q&A site, a question asked in July 2025 about exactly this drew an answer from a support specialist profile that laid out a complete flow, ending with a conversion event that fires on a closed deal and a matching goal on the ad platform to receive it. The person who asked replied that the option the answer described was not in his account, and that he could not find documentation for a native integration anywhere. A later responder called that answer a hallucination and said it would have been correct for three other ad platforms, which is likely where it came from. In his own answer on the thread he wrote: “as of right now there is no native integration with Microsoft Ads”. We read the thread recently to check a conclusion we had already reached.
The answer was convincing because its shape was right. An answer shaped correctly for three other platforms is the hardest kind to catch, because nothing about it looks wrong until somebody goes looking for the part that does not exist.
What the account looked like the day we started
When we opened the account, seven conversion goals existed. None of them had a revenue value assigned to it. Every campaign in the account therefore reported zero revenue. An account that reports no revenue cannot be bid on revenue. The platform made every automated decision against a count of form fills, with nothing in the account able to tell it that one form fill was worth more than another.
One goal was producing conversions. The other six had produced none. Somebody had switched five of those six into automated bidding anyway. One of them was named for testing. One had been created by the platform itself. One came from a platform recommendation dated 2023. No offline conversion goal existed on the account, and nobody had ever uploaded one.
This was the state we inherited on day one, built up over several years by different hands and by the platform’s own automatic goal creation. We took the five dead goals out of automated bidding and left them recording. We deleted nothing.

The identifier was not in the field built for it
Matching an outcome back to an ad click depends on a click identifier, a code the ad platform attaches to the click so a sale weeks later can be traced to it. The CRM has a dedicated property for that identifier. It was empty on every single contact in the account. We checked it two ways before we accepted that.
The identifier itself was present on 98% of contacts the CRM had attributed to the platform across the preceding months. It was somewhere the CRM was never going to send on its own. What we could see covers a contact’s first visit only, so somebody who arrived another way first and clicked an ad later carries no identifier at all.

A full data set that read as empty
We built our first real file from 28 contacts that had recently reached a stage qualifying for upload. The identifier extraction returned 0 matches out of 28. A single character in the way the CRM hands that field back is the whole explanation.
A silent zero here does not weaken the upload; it loses it entirely. When the goal has not been set up to match on hashed contact details (customer email addresses and phone numbers converted into codes that cannot be read back) and a row in the file arrives with no click identifier, Microsoft rejects the entire file, not just that row. Microsoft’s documentation puts it in one line: “If it doesn’t contain an msclkid, the file will be rejected.” The first cost is that the file does not land. The second is diagnostic: a zero-match result looks like an account with no matchable data in it, which sends somebody off to argue about lead quality or tracking coverage, and nothing in the number itself says the data was there the whole time.

The upload that failed on every row, and the upload that succeeded on every row
Our first upload came back with every one of its 28 rows rejected, all of them for the same reason: a goal has to exist for two hours before anything can be uploaded to it. Microsoft states it plainly: “After creating a conversion goal, you’ll need to wait two hours before uploading the file. It can take up to six hours to view uploaded conversion data.” That rejection was the harmless kind. Nothing was applied to the account, nothing was recorded on our side as sent, and the retry needed no cleanup.
Our first real send was accepted without any rejections. We did not treat that as finished, because Microsoft documents the failure mode itself: “Click IDs that are too old or not associated with your account will still appear as having been uploaded successfully because the format is valid.” The same page notes that duplicates carrying the same identifier, conversion name and conversion time are not imported twice.
A clean response means the file parsed, not that a conversion was recorded. If nothing counted, the bidding still runs on form fills, the budget still buys the same mix of leads it was buying before, and the account now has a green light on the dashboard saying the outcome data is flowing. A silent failure with a success message on it is worse than no integration at all, because a success message removes the reason to look.
So we reconcile what we sent against what actually appeared, after the reporting delay, instead of reading the status column. The status column reports on a file that was uploaded. The documentation names no alert for a scheduled run that fails, so that check comes from our side. The platform’s counting mode also decides whether a given outcome can be sent on an unattended schedule at all. On one of the three stages we send, a repeat send would add revenue that never existed, so that one goes out attended instead of automatically.
We ruled out free options by how they would handle customer data
We had a hosting recommendation, and we reversed it when the budget for it went to zero. Every genuinely free alternative we examined failed on how it would handle a file of hashed customer contact data. Price never decided any of those failures. The route we took still has a cost, named on day one: it depends on a credential with a capped lifetime that stops working on a date the job itself never flags in advance, so that date went on a calendar the day the credential was created.
What this does not do, and one next step
The capture misses contacts whose first visit was not the ad click. A 90-day ceiling applies twice, from click to conversion and from conversion to upload, so a sales cycle longer than a quarter undercounts by design. At the time of writing our first send is accepted and not yet confirmed as counted. We will read the result the following morning. This is one account and one day of work, reported as what happened.

The work is a build: somebody decides which outcomes are worth counting, gets the identifier out of the CRM intact, proves the format once, and then checks that what was sent is what landed. Synthesis Insights does that work for B2B advertisers whose leads talk to a salesperson before they buy. Whether the channel earns its budget is a separate question answered with performance data, something we explored in a post comparing Microsoft Ads and Google Ads. If you want to know whether your outcome data is reaching the platform at all, we will read your account and your CRM and tell you.
FAQ
Does HubSpot have a native integration with Microsoft Ads for offline conversions?
No. HubSpot publishes documentation for creating and syncing ad conversion events with Google Ads, Meta and LinkedIn, and there is no equivalent article for Microsoft. It does list Microsoft among the ad accounts a customer can connect, and its Microsoft-specific pages are in beta and cover connecting the account, campaign tracking and reporting. On Microsoft’s own question and answer site, a responder wrote: “as of right now there is no native integration with Microsoft Ads”.
Our offline conversion upload came back successful. Does that mean the conversions counted?
No. A clean response means the file parsed, not that a conversion was recorded. Microsoft documents the failure mode itself: “Click IDs that are too old or not associated with your account will still appear as having been uploaded successfully because the format is valid.” If nothing counted, the bidding still runs on form fills, the budget still buys the same mix of leads it was buying before, and the account now has a green light on the dashboard saying the outcome data is flowing. So we reconcile what we sent against what actually appeared, after the reporting delay, instead of reading the status column.
Why was our whole upload file rejected when only some rows were missing a click ID?
That is how Microsoft handles it. When the goal has not been set up to match on hashed contact details, and a row in the file arrives with no click identifier, Microsoft rejects the entire file, not just that row. Microsoft’s documentation puts it in one line: “If it doesn’t contain an msclkid, the file will be rejected.” The first cost is that the file does not land. The second is diagnostic: a zero-match result looks like an account with no matchable data in it, which sends somebody off to argue about lead quality or tracking coverage.
Our CRM’s click ID field is empty on every contact. Does that mean the data is not there?
Not necessarily. In the account we worked, the CRM’s dedicated property for that identifier was empty on every single contact, and we checked it two ways before we accepted that. The identifier itself was present on 98% of contacts the CRM had attributed to the platform across the preceding months. It was somewhere the CRM was never going to send on its own.
Our sales cycle is longer than three months. Will offline conversions still work?
A 90-day ceiling applies twice, from click to conversion and from conversion to upload, so a sales cycle longer than a quarter undercounts by design. The capture also misses contacts whose first visit was not the ad click, because what we could see covers a contact’s first visit only.
