Using __tcfapi Event Listeners: A Developer Tutorial for Consent-Aware Scripts

Why Your Scripts Should Listen, Not Poll

Any script that depends on consent — an ad tag, an analytics loader, a header bidding wrapper — needs to know two things: whether the CMP has a decision yet, and what that decision is. A common mistake is to read a cookie on page load, or to poll the CMP with a timer until something appears. Both approaches break in predictable ways: the cookie format is internal to the CMP and can change, and polling either fires too early or wastes time waiting.

The IAB TCF defines a standard JavaScript interface for exactly this job: the __tcfapi function. This tutorial walks through the commands you actually need, the events you will receive, and the bugs that show up most often in production.

The Stub and the Queue

A TCF CMP is expected to place a small stub on the page as early as possible, before the full CMP script loads. The stub defines window.__tcfapi and queues any calls made before the CMP is ready. When the CMP finishes loading, it processes the queue and answers each call.

This is what makes it safe for other scripts to call __tcfapi immediately: as long as the stub is in the page before them, their calls are not lost. If a script throws because __tcfapi is undefined, the stub is either missing or loading too late — fix the load order rather than adding retries.

The Commands That Matter

ping

__tcfapi('ping', 2, callback) returns basic status without waiting for a user decision. The response includes cmpLoaded, a cmpStatus such as stub, loading, loaded or error, and a displayStatus of visible, hidden or disabled. It also reports gdprApplies once known. Use it for diagnostics and to decide whether the TCF is relevant for this visitor at all.

addEventListener

__tcfapi('addEventListener', 2, callback) is the command most integrations should use. The callback receives a tcData object and a success flag, and it is called again every time the consent state changes. Since TCF v2.2, this is the recommended way to get consent data; the older getTCData command was deprecated.

removeEventListener

Each tcData object carries a listenerId. Pass it to __tcfapi('removeEventListener', 2, callback, listenerId) when your script no longer needs updates — for example, a one-time loader that has already made its decision.

Reading the eventStatus

The key field in tcData is eventStatus. There are three values:

A typical loader waits for either tcloaded or useractioncomplete, then checks the purposes and vendors it needs. It ignores cmpuishown except perhaps to log that a banner appeared.

Checking Purposes and Vendors

Inside tcData, consent and legitimate interest are exposed as objects keyed by ID:

Check both the purpose and the vendor. A purpose consent with no vendor consent, or the reverse, is not enough for most vendors to process data.

Iframes and Cross-Frame Calls

Ads and widgets often run inside iframes that cannot see the parent page’s __tcfapi. The TCF solves this with a postMessage protocol: the CMP adds a frame named __tcfapiLocator to the top page, and code inside an iframe can locate it, send a __tcfapiCall message and receive a __tcfapiReturn response. Most ad SDKs already do this. If you write your own iframe code, implement the same pattern rather than reading cookies across frames.

Common Bugs and How to Spot Them

  1. Acting on cmpuishown. Scripts that load as soon as any callback fires will run before the user decides. Filter on eventStatus.
  2. Removing the listener too early. If you remove it after the first callback, you will miss a later useractioncomplete and never load.
  3. Ignoring withdrawals. Persistent scripts should keep listening and stop or reconfigure themselves when consent is withdrawn.
  4. Ignoring gdprApplies. Outside TCF regions, purpose objects may be empty; treating that as “no consent” can switch off revenue you are allowed to earn.
  5. Not checking the success flag. When success is false, log it; it usually signals a broken CMP setup.

Testing Your Integration

In the browser console, call __tcfapi('ping', 2, console.log) to confirm the CMP is present, then register a listener with console.log as the callback and click through the banner. You should see cmpuishown followed by useractioncomplete on a first visit, and tcloaded on a reload. Repeat with accept-all, reject-all and a custom selection, and confirm your script behaves correctly in each case.

Key Takeaways

← Blog Read All →