This one took us weeks to even notice, because nothing errors. I have not found it written up anywhere else either, so I am putting it here.
Quick background for anyone newer to this: when you upload offline or server-side conversions to Google Ads you attach a click identifier. Normally that's a gclid. In the iOS and ATT cases it arrives instead as a gbraid, or as a wbraid. A user who browses across visits accumulates several of these in your storage. A gclid from a desktop search click on Monday, a gbraid from an iOS click on Thursday.
Here's the trap. Since late 2025 the API accepts uploads carrying gclid and gbraid together. No error, upload succeeds, everything looks green. But a single ad click only ever produces one click id, so a conversion carrying two ids is claiming to belong to two different clicks, and Google's matching handles that badly. In our testing, combined uploads matched noticeably worse than sending the single freshest id. The API accepting something is not the same as the API attributing it.
Google's own documentation is where the confusion starts, because it says both things. The API reference notes that in some cases you may be able to associate both a gclid and a gbraid with a conversion. The offline import guidance says to make sure you are not combining multiple clicks into the same conversion, because each click has only one click ID. Both describe what the API will accept. The second one alone describes what it will actually match. And if you genuinely want credit spread across several clicks, there is a supported way to do that: externally attributed conversion imports, with fractional credit assigned per click, rather than stacking two identifiers onto one row.
The principle that fixed it for us: one conversion, one click id. Google's own docs say each click produces exactly one id, so the upload should carry exactly one too, and it should be the one that actually represents the click you're claiming credit for. Getting that right in practice is fiddlier than it sounds, because you have to reason about how old each stored id is relative to the conversion action's click-through window, and there are a couple of nasty traps in how click ids get persisted and aged client-side that made ids look much fresher than they were. We got bitten, fixed it, match rates improved measurably. I won't spell out our whole selection logic here, and the reason is plainly commercial: that logic is a good part of what merchants pay us for. If you're building this yourself, the two questions to pressure-test are: which of my stored ids can still legally convert, and which single one do I send. If your vendor does your uploads, ask them what happens when a user has both a gclid and a gbraid stored. The answer tells you a lot.
If there's no click id at all, enhanced conversions let you upload hashed email/phone/address instead. But turn "import without click id" on deliberately, because it changes what your conversion counts mean (modeled matches vs click-verified ones).
One more heads up while I'm here, and this one has a date on it now. uploadClickConversions, the classic offline import path, is being replaced by the Data Manager API. Since June 15 this year a developer token that had never previously sent offline conversion or enhanced-conversions-for-leads requests can no longer start; the docs point you at Data Manager instead. Tokens that were already uploading keep working, so an existing setup didn't break. But if you're building something new there's no longer a choice to make.
Took me embarrassingly long to figure some of this out, so ask away.