c15t
Version:
Headless JavaScript consent management platform for cookie banners, privacy preferences, consent storage, and script gating.
164 lines (127 loc) • 5.85 kB
Markdown
---
title: Callbacks
description: React to consent lifecycle events — initialization, consent
changes, errors, and revocation reloads.
group: frameworks
---
Callbacks let you run custom code at key points in the consent lifecycle. Define them in the provider or runtime `callbacks` option, or register them dynamically after initialization.
For analytics SDKs and other change-only integrations, prefer `subscribeToConsentChanges()` or `onConsentChanged`. Use `onConsentSet` when you want the broader lifecycle signal, including initialization, automatic defaults, and replay-aware registration.
> ℹ️ **Info:**
> consentStore.getState().subscribeToConsentChanges() is the recommended API for analytics SDKs and consent-mode integrations. It only emits future saves that actually changed persisted preferences.
Define callbacks in the runtime options:
```ts
import { getOrCreateConsentRuntime } from 'c15t';
const { consentStore } = getOrCreateConsentRuntime({
mode: 'hosted',
backendURL: 'https://your-instance.c15t.dev',
callbacks: {
onBannerFetched: ({ jurisdiction, location, translations }) => {
console.log('Jurisdiction:', jurisdiction);
console.log('Country:', location.countryCode);
console.log('Language:', translations.language);
},
onConsentSet: ({ preferences }) => {
console.log('Consent lifecycle event:', preferences);
},
onConsentChanged: ({ allowedCategories, deniedCategories }) => {
analytics.syncConsent({ allowedCategories, deniedCategories });
},
onError: ({ error }) => {
errorReporter.captureMessage(error);
},
onBeforeConsentRevocationReload: ({ preferences }) => {
// Flush pending analytics before page reloads
analytics.flush();
},
},
});
```
|Surface|Replays when registered late?|Fires on init / hydration / auto-grants?|Best for|
|--|--|--|--|
|`onBannerFetched`|Yes, via `setCallback('onBannerFetched', ...)` after init|Yes|Logging resolved policy, location, and translations|
|`onConsentSet`|Yes, via `setCallback('onConsentSet', ...)`|Yes|Broad lifecycle hooks, debugging, and integrations that want the latest full state regardless of how it was reached|
|`onConsentChanged`|No|No|Declarative change-only integrations|
|`subscribeToConsentChanges()`|No|No|Canonical change-only subscriptions after mount|
> ℹ️ **Info:**
> Script.onConsentChange is a script-scoped lifecycle hook. It is not the global consent change API for analytics SDKs or other app-wide integrations.
Called when the consent banner data is fetched from the backend (or loaded from SSR data). The payload includes jurisdiction info, location data, and resolved translations.
```tsx
onBannerFetched: ({ jurisdiction, location, translations }) => {
// jurisdiction: 'GDPR' | 'CCPA' | { code: 'GDPR', message: '...' } | ...
// location: { countryCode: 'DE', regionCode: 'BY' }
// translations: { language: 'de', translations: {...} }
}
```
Called whenever c15t broadly settles consent state: store initialization, automatic defaults during init, explicit saves, and replay via `setCallback('onConsentSet', ...)`.
```tsx
onConsentSet: ({ preferences }) => {
// preferences: { necessary: true, measurement: true, marketing: false, ... }
console.log('Latest consent state:', preferences);
}
```
Called only after an explicit `saveConsents()` or `setConsent()` that actually changes the saved consent state. It never fires on store creation, hydration, automatic grants, unchanged saves, or `setCallback('onConsentChanged', ...)`.
```tsx
onConsentChanged: ({
preferences,
previousPreferences,
allowedCategories,
deniedCategories,
previousAllowedCategories,
previousDeniedCategories,
}) => {
analytics.syncConsent({
allowedCategories,
deniedCategories,
previousAllowedCategories,
previousDeniedCategories,
});
}
```
Called when an error occurs during consent operations (e.g., API request failure). If no `onError` callback is provided, errors are logged to `console.error`.
```tsx
onError: ({ error }) => {
// error: string describing what went wrong
Sentry.captureMessage(`Consent error: ${error}`);
}
```
Called synchronously before the page reloads due to consent revocation. This is your last chance to run cleanup before the reload. Keep this callback fast - avoid async operations.
```tsx
onBeforeConsentRevocationReload: ({ preferences }) => {
// Flush any pending data
navigator.sendBeacon('/api/flush', JSON.stringify({ session: sessionId }));
}
```
Use `subscribeToConsentChanges()` when you want a listener for real preference changes after runtime creation:
```ts
const unsubscribe = consentStore
.getState()
.subscribeToConsentChanges(({ allowedCategories, deniedCategories }) => {
analytics.syncConsent({ allowedCategories, deniedCategories });
});
// Later, when you're done listening:
unsubscribe();
```
Register or update callbacks at runtime using `setCallback()`:
```ts
const state = consentStore.getState();
state.setCallback('onBannerFetched', ({ jurisdiction, location }) => {
console.log('Resolved init data:', { jurisdiction, location });
});
state.setCallback('onConsentSet', ({ preferences }) => {
console.log('Broad consent lifecycle event:', preferences);
});
// Remove callbacks again when no longer needed
state.setCallback('onBannerFetched', undefined);
state.setCallback('onConsentSet', undefined);
```
`setCallback('onConsentSet', ...)` immediately replays the current consent state. For change-only logic, prefer `subscribeToConsentChanges()` or `onConsentChanged`.