Skip to main content

JavaScript Consent API

Callbacks

Pick the hook for the job

You want toUse
Start or stop your own code as permissions changecallbacks.onPermissionsChanged, or the client's consent event
Know that a visitor clicked accept, reject or savecallbacks.onChoiceRecorded
Wait until c15t knows the visitor's permissionsconsent.ready(), or the ready event
Show, hide or switch your own UIThe client's ui event, or kernel.subscribe
Report failed backend callscallbacks.onError, or the error event
Flush data before the reload after a withdrawalcallbacks.onBeforeConsentRevocationReload

Prefer gating a vendor through scripts where you can. The loader starts and stops it for you, and its own onLoad and onConsentChange callbacks cover per-vendor work; see the script loader.

Pass callbacks

callbacks works the same in init() from @c15t/browser and in createConsentRuntime:

src/main.ts
import { hosted, init } from '@c15t/browser';

import { scripts } from './scripts';

export const consent = init({
	callbacks: {
		// Only a visitor's own accept, reject or save.
		onChoiceRecorded: ({ confirmed, snapshot }) => {
			console.info('Visitor confirmed', confirmed, snapshot.explicitChoice);
		},
		onError: ({ error }) => {
			console.warn('Consent request failed:', error);
		},
		// Any change to what may run, including a restored choice.
		onPermissionsChanged: ({ previous, snapshot }) => {
			console.info('Permissions', previous, snapshot.effectivePermissions);
		},
	},
	mode: hosted({ backendURL: 'https://your-project.inth.app' }),
	scripts,
});

// The surface c15t wants shown: 'banner', 'dialog' or 'none'.
consent.on('ui', (surface) => {
	document.documentElement.dataset.consentSurface = surface ?? 'none';
});
CallbackPayloadWhen it runs
onChoiceRecorded{ snapshot, confirmed, actionAt }A visitor accepted, rejected or saved. confirmed lists the categories this action recorded.
onPermissionsChanged{ snapshot, previous }Effective permissions changed value, for any reason. previous is the old permission map.
onError{ error }, a message stringA command failed, such as /init or a save request.
onBeforeConsentRevocationReload{ preferences }Right before the reload that follows a withdrawal. Runs synchronously; keep it short.

Callbacks are read when the client or runtime is created.

A recorded choice is not a permission change

  • onChoiceRecorded runs only for a visitor's own accept, reject or save. It does not run when a stored choice is restored at load, when a choice expires, or when a notice is dismissed. Use it for analytics about the decision itself.
  • onPermissionsChanged runs whenever what may run changes. That includes a save that changed something, a stored choice restored at load, a policy resolving, a choice expiring and a GPC signal. Use it to start and stop your code.

A save that leaves every permission as it was can run onChoiceRecorded without onPermissionsChanged. Never call a save from onPermissionsChanged; that turns a permission into a recorded choice the visitor did not make. How consent works explains the difference.

Client events

The @c15t/browser client also emits events. consent.on(event, listener) returns an unsubscribe function:

EventPayloadWhen it fires
readythe snapshotThe policy has resolved and the UI can render. For a returning visitor, hasConsented() is reliable from here.
consentthe snapshotPermissions, the recorded choice or the vendor switches changed. This includes a stored choice restored at start, a save, an expiry and a GPC change.
ui'banner', 'dialog' or 'none'The surface c15t wants shown changed.
errorthe errorA backend call failed, or an IAB step failed.

A ready listener added after the policy resolved runs at once with the snapshot from that moment. A ui listener added after that runs at once with the current surface. consent and error listeners only hear later changes.

Each event is also dispatched on document as c15t:ready, c15t:consent, c15t:ui and c15t:error, with the payload in event.detail, for code that cannot import the client.

Kernel events

A runtime or a kernel you own has no client events. Listen on the kernel instead:

const stop = runtime.kernel.events.on('choice:recorded', ({ confirmed }) => {
	console.info('Visitor confirmed', confirmed);
});

The four callbacks are built on choice:recorded, permissions:changed and command:error. The kernel reference lists every event. To attach the callbacks to a kernel you created yourself, call wireRuntimeCallbacks({ kernel, callbacks }) from c15t/runtime.

Errors thrown by your code

A callback or kernel listener that throws does not stop other listeners or fail the action. c15t reports it with reportError in the browser.

A listener added with on() on the @c15t/browser client that throws is logged with console.error. The later on listeners for the same event and the matching c15t:* event on document still run. This covers the ready and ui listeners that on() calls at once after the policy resolved, so on() still returns its unsubscribe function.

Check it works

  1. Add the callbacks above and open the app in a private window under an opt-in policy. Nothing logs, because nothing is allowed and nobody chose.
  2. Click Accept All. Both onChoiceRecorded and onPermissionsChanged log.
  3. Reload. onPermissionsChanged logs the restored permissions and onChoiceRecorded stays silent.