redacto_consent_sdk
Flutter SDK for integrating Redacto consent notices and a self-service Privacy Center into your app.
Installation
flutter pub add redacto_consent_sdk
Requirements
- Dart
>=3.3.0 <4.0.0 - Flutter
>=3.19.0
Architecture
The SDK follows a client-server architecture pattern:
- Your Backend Server acts as a proxy between your Flutter application and Redacto's API, handling token generation and refresh.
- Your Flutter Application uses SDK widgets to display consent notices and collect user choices.
- SDK Widgets fetch consent content, render UI, and submit consent records.
Before You Start
You need three UUIDs from your Redacto Console and a CMS API Key.
Where to Find Your UUIDs
| Value | Where to find it |
|---|---|
| Organisation UUID | Console → Settings → Organization → Copy UUID (or from the URL: /org/{ORG_UUID}/...) |
| Workspace UUID | Console → Settings → Workspaces → Select your workspace → Copy UUID (or from the URL: /org/.../workspace/{WORKSPACE_UUID}/...) |
| Notice UUID | Console → Consent Management → Notices → Click on your notice → Copy UUID (or from the URL: .../notices/{NOTICE_UUID}) |
CMS API Key
Your backend needs a CMS API Key to call the Redacto token API. This key must never appear in frontend code.
Important: You need to obtain your CMS API Key from the Redacto Dashboard. Store this key securely in your environment variables and never expose it to the frontend.
- Go to Console → Settings → API Keys.
- Click Create API Key.
- Copy the key — you will not see it again.
- Store it as an environment variable on your server:
export CMS_API_KEY="your-api-key-here"
Required App Configuration
Store the following values in your app configuration (for example, Dart defines, secure config, or remote config):
| Config Value | Required | Used For |
|---|---|---|
BACKEND_URL |
Yes | Your backend endpoint for consent token and refresh token APIs |
NOTICE_UUID |
Yes (modal flows) | Notice rendered by RedactoNoticeConsent |
ORGANISATION_UUID |
Yes (inline flows) | Loading consent content and token requests |
WORKSPACE_UUID |
Yes (inline flows) | Loading consent content and token requests |
BASE_URL |
No | Override the default Redacto consent API base URL |
How to wire these values:
- Set these values in your app's runtime/build configuration.
- Send
ORGANISATION_UUID,WORKSPACE_UUID, and optionalBASE_URLto your backend token endpoint. - Use returned
accessTokenandrefreshTokenwithRedactoNoticeConsent, or pass UUIDs (and tokens when available) toRedactoNoticeConsentInline.
Components
RedactoNoticeConsent
Modal consent component for initial consent and reconsent flows.
Best for: Initial consent collection, standalone consent flows
Key Characteristics:
- Requires tokens upfront (must be provided before rendering)
- Displays as a modal overlay
- Blocks UI interaction by default
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
noticeId |
String |
Yes | - | Notice UUID |
accessToken |
String |
Yes | - | JWT access token |
refreshToken |
String |
Yes | - | JWT refresh token |
baseUrl |
String? |
No | https://api.redacto.io/consent |
Override consent API base URL (notice fetch, tokens, legacy writes) |
ledgerBaseUrl |
String? |
No | - | Go ledger base URL. When set, consent writes and prior-consent checks route through the ledger (falls back: ledger → baseUrl → default). Omit to keep the legacy consent-server path. |
settings |
ConsentSettings? |
No | - | UI styling overrides |
language |
String |
No | "en" |
Initial language |
blockUI |
bool |
No | true |
Prevents back-dismiss |
onAccept |
VoidCallback |
Yes | - | Called on accept |
onDecline |
VoidCallback |
Yes | - | Called on decline |
onError |
ValueChanged<Object>? |
No | - | Error callback |
applicationId |
String? |
No | - | Application/user-specific UUID |
validateAgainst |
ValidateAgainst |
No | ValidateAgainst.all |
Validation mode |
includeFullyConsentedData |
bool |
No | false |
Include review payload for already-consented users |
reviewModeButtonText |
String? |
No | "Update Consent" UI path |
Custom review CTA text |
onPrivacyCenterTap |
VoidCallback? |
No | - | Called instead of opening the privacy center URL in an external browser; use for in-app Privacy Center navigation |
When onPrivacyCenterTap is set, tapping the privacy center link in the notice routes to your handler (for example, Navigator.push to a screen hosting RedactoPrivacyCenter). See Privacy Center.
RedactoNoticeConsentInline
Embedded consent component for inline form contexts.
Best for: Registration forms, checkout flows, multi-step processes
Key Characteristics:
- Renders inline within your form
- Auto-submits consent when all conditions are met
Auth paths: The inline widget supports two mutually exclusive auth paths — provide either noticeId + accessToken (post-token) OR orgUuid + workspaceUuid + noticeUuid (pre-token: render the widget while you fetch tokens). Params marked No* in the table below are conditionally required: exactly one of the two combos must be provided.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
noticeId |
String? |
No* | - | Notice UUID (post-token path) |
accessToken |
String? |
No* | - | JWT access token (post-token path) |
orgUuid |
String? |
No* | - | Organisation UUID (pre-token path) |
workspaceUuid |
String? |
No* | - | Workspace UUID (pre-token path) |
noticeUuid |
String? |
No* | - | Notice UUID (pre-token path) |
baseUrl |
String? |
No | https://api.redacto.io/consent |
Override consent API base URL (notice fetch, legacy writes) |
ledgerBaseUrl |
String? |
No | - | Go ledger base URL. When set, consent writes and already-consented checks route through the ledger. Applies to the post-token path only (both require an accessToken). Omit for legacy behavior. |
settings |
ConsentSettings? |
No | - | UI styling overrides |
language |
String |
No | "en" |
Initial language |
onAccept |
VoidCallback? |
No | - | Called after auto-submit |
onDecline |
VoidCallback? |
No | - | Decline callback |
onError |
ValueChanged<Object>? |
No | - | Error callback |
onValidationChange |
ValueChanged<bool>? |
No | - | Emits validation state |
applicationId |
String? |
No | - | Application/user-specific UUID |
validateAgainst |
ValidateAgainst |
No | ValidateAgainst.required |
Validation mode |
includeFullyConsentedData |
bool |
No | false |
Include review payload for already-consented users |
Usage Example
import "package:flutter/material.dart";
import "package:redacto_consent_sdk/redacto_consent_sdk.dart";
class ConsentDemo extends StatelessWidget {
const ConsentDemo({super.key});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (_) => RedactoNoticeConsent(
noticeId: NOTICE_UUID,
accessToken: accessToken,
refreshToken: refreshToken,
validateAgainst: ValidateAgainst.all,
onAccept: () => Navigator.of(context).pop(),
onDecline: () => Navigator.of(context).pop(),
onError: (error) => debugPrint(error.toString()),
),
);
},
child: const Text("Open consent"),
);
}
}
Styling (ConsentSettings)
ConsentSettings supports:
- Base properties:
link,borderRadius,backgroundColor,headingColor,textColor,borderColor,font - Buttons:
button.accept(backgroundColor,textColor)button.decline(backgroundColor,textColor)button.language(backgroundColor,textColor,selectedBackgroundColor,selectedTextColor)
Privacy Center
A self-service Privacy Center widget for managing consents, submitting data subject access requests (DSAR), and tracking activity. Renders as a mobile-first single column (matching React Native): top navbar (page title, email, language selector, theme toggle), page content, bottom tab bar (Consents | Requests | Activities | Receipts), and a "Powered by Redacto" footer. Case details is an internal overlay reached from the Requests tab—not a bottom-tab page.
baseUrl is the consent API base URL (same as notice flows, for example https://api.redacto.io/consent), not a separate privacy center URL.
Import
import "package:redacto_consent_sdk/redacto_consent_sdk.dart";
RedactoPrivacyCenter, PrivacyCenterPage, and related Privacy Center exports are available from the main library entry—no separate import path (unlike React Native's /privacy-center subpath).
Dependencies
Privacy Center features rely on packages bundled as direct SDK dependencies: intl, file_picker, share_plus, open_filex, and path_provider. Receipt download/share and DSAR file upload use the platform share sheet and file picker; follow standard Flutter iOS and Android setup for those plugins. You do not need to add these packages separately when using redacto_consent_sdk.
RedactoPrivacyCenter
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl |
String |
Yes | - | Consent API base URL for Privacy Center requests |
ledgerBaseUrl |
String? |
No | - | Go ledger base URL. When set, user consents, receipts, and manage-consent route through the ledger. Omit to use the legacy /dsar/privacy-center/* routes. See the note below on the DSAR form picker. |
accessToken |
String |
Yes | - | JWT access token |
refreshToken |
String |
Yes | - | JWT refresh token |
onError |
void Function(Object error) |
Yes | - | Error handler |
theme |
Brightness |
No | Brightness.light |
Visual theme (Brightness.light or Brightness.dark); user can toggle from the navbar |
initialPage |
PrivacyCenterPage |
No | PrivacyCenterPage.consentManager |
Initial tab: consentManager, form, activity, or receipt. Do not use caseDetails—it is internal and opened from the Requests list |
language |
String |
No | "en" |
Initial language code (for example en, hi); drives UI strings and the API language query param; user can change from the navbar |
contactEmail |
String? |
No | - | User contact email for case history and forms when the JWT does not embed it (common with backend-issued consent tokens) |
onBack |
VoidCallback? |
No | - | When set, the navbar shows a back arrow that invokes this callback (for example Navigator.of(context).pop()) |
Usage Example
import "package:flutter/material.dart";
import "package:redacto_consent_sdk/redacto_consent_sdk.dart";
class PrivacyCenterScreen extends StatelessWidget {
const PrivacyCenterScreen({
super.key,
required this.accessToken,
required this.refreshToken,
this.userEmail,
});
final String accessToken;
final String refreshToken;
final String? userEmail;
@override
Widget build(BuildContext context) {
return Scaffold(
body: RedactoPrivacyCenter(
baseUrl: BASE_URL,
accessToken: accessToken,
refreshToken: refreshToken,
contactEmail: userEmail,
theme: Brightness.light,
initialPage: PrivacyCenterPage.consentManager,
onBack: () => Navigator.of(context).pop(),
onError: (error) => debugPrint(error.toString()),
),
);
}
}
Ledger mode
Passing ledgerBaseUrl routes consent-bearing reads and writes to the Go ledger:
- Notice widgets —
submit-consentgoes to the ledger, and the already-consented check uses the ledger'scheck-consentendpoint instead of the legacy HTTP 409 signal. Notice content and tokens always stay onbaseUrl. - Privacy Center — user consents, receipts (including the
/downloadPDF path), and manage-consent (sent as apartial: truewrite) route to the ledger. DSAR form, cases, activities, uploads, and OTP stay onbaseUrl.
ledgerBaseUrl is independent of baseUrl — you still pass both. Every ledger read degrades to the legacy path on failure, so a ledger outage does not break the consent flow.
Known limitation. The DSAR form is not ledger-aware. It sources its purpose list from Python's
form/dataflatpurposesarray and has no product-grouped picker (the React and React Native SDKs group DSAR targets by product and, in ledger mode, source that picker from ledger-served consents — Flutter has no equivalent, andform/data'sdirect_groups/nominated_groupsare not parsed). SettingledgerBaseUrldoes not change the form. If your backend'sform/datastops returningpurposesfor a workspace cut over to the ledger, the form's purpose list will be empty.
Headless API
For fully custom UIs, the package also exports headless building blocks on top of the same backend:
PrivacyCenterApiandPrivacyCenterAuthManager— API client and token refreshPrivacyCenterPage,PCScope,PcTheme— navigation scope and theming- Models from
privacy_center_models.dart(cases, consents, activities, receipts, etc.)
See the example app for a runnable integration.
Sandbox Mode (No Backend Required)
Sandbox mode lets you build and test a full consent PoC — notice → Privacy Center → DSAR — straight from your app, with no backend and no token minting. You paste a static sandbox token into the widget instead of your server minting a JWT. Everything is recorded as test data, isolated from live records. Going live is a config swap, not a rewrite.
Sandbox is available on the modal RedactoNoticeConsent and on RedactoPrivacyCenter. The inline RedactoNoticeConsentInline widget has no sandbox mode.
1. Get your sandbox credentials
Redacto Dashboard → API Keys → Sandbox tab: copy the Sandbox token (static, never expires). Copy your Organization ID and Workspace ID from the same page under Workspace identifiers.
2. Collect consent
Pass the sandbox token plus org/workspace and a test identity to RedactoNoticeConsent — no accessToken, no refreshToken:
import "package:flutter/material.dart";
import "package:redacto_consent_sdk/redacto_consent_sdk.dart";
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (_) => RedactoNoticeConsent(
noticeId: NOTICE_UUID,
token: SANDBOX_TOKEN,
organisationUuid: ORGANISATION_UUID,
workspaceUuid: WORKSPACE_UUID,
email: "test.user@example.com", // or mobile / ucic
onAccept: () => Navigator.of(context).pop(),
onDecline: () => Navigator.of(context).pop(),
onError: (error) => debugPrint(error.toString()),
),
);
3. Manage consent & raise a DSAR
Give RedactoPrivacyCenter the same token, org, workspace, and test identity. The same test user can review their consent, withdraw it, and file a DSAR — all recorded as test data:
import "package:flutter/material.dart";
import "package:redacto_consent_sdk/redacto_consent_sdk.dart";
Scaffold(
body: RedactoPrivacyCenter(
baseUrl: BASE_URL,
token: SANDBOX_TOKEN,
organisationUuid: ORGANISATION_UUID,
workspaceUuid: WORKSPACE_UUID,
email: "test.user@example.com", // or mobile / ucic — same identity as step 2
onBack: () => Navigator.of(context).pop(),
onError: (error) => debugPrint(error.toString()),
),
);
Sandbox props
These are added to both RedactoNoticeConsent and RedactoPrivacyCenter. In sandbox mode, accessToken and refreshToken are omitted.
| Parameter | Type | Required | Description |
|---|---|---|---|
token |
String? |
Yes | Static sandbox token. Its presence activates sandbox mode. |
organisationUuid |
String? |
Yes | Organization ID (replaces the JWT-decoded org in sandbox mode). |
workspaceUuid |
String? |
Yes | Workspace ID (replaces the JWT-decoded workspace in sandbox mode). |
email |
String? |
One of email/mobile/ucic | Test subject email. |
mobile |
String? |
One of email/mobile/ucic | Test subject mobile (used when email is absent). |
ucic |
String? |
One of email/mobile/ucic | Client's own user id (org_user_id). Takes precedence: ucic > email > mobile. |
How it works
- The sandbox token is sent as the single
X-Consent-Tokenheader; its presence marks the request as test data. There is noAuthorization: Bearerand no JWT refresh, so a401fails fast instead of retrying. - The acting identity rides each request's own payload — query params (
org_user_id/primary_email/primary_mobile) on reads, the JSON body on writes — resolved in precedence orderucic>email>mobile. - The server records everything under
environment=test, namespaced and isolated. Test data never mixes with, or reads from, your live records. - Sandbox always talks to the consent server; it never routes to the Go ledger, so
ledgerBaseUrlhas no effect in sandbox mode.
Going live
Use the same widgets. Remove the sandbox props (token, organisationUuid, workspaceUuid, and the test identity) and pass a real accessToken / refreshToken JWT minted by your backend. Records then write as environment=live.
Localization
Notice consent
- Set initial locale with
languageonRedactoNoticeConsentorRedactoNoticeConsentInline. - UI labels and text content are loaded from notice translations.
- Users can switch languages via the built-in selector when translations are available.
Privacy Center
- Set initial locale with
languageonRedactoPrivacyCenter. - Privacy Center ships built-in UI strings for many locales; users switch language from the navbar selector.
languagedrives both UI strings and API-backed labels where applicable (request types, statuses, purposes).- Date and relative-time formatting uses
intlinternally; the SDK initializes locale data as needed—no extra app setup required.
Troubleshooting
- No modal data: verify
noticeId,accessToken, andrefreshToken. - Inline submit not firing: ensure required selections are complete and
onValidationChangebecomestrue. - HTTP errors: check backend token generation and
baseUrlvalue. - Empty case history or case API errors: pass
contactEmailwhen your JWT does not include the user's email. - Wrong language on case details after toggle: the SDK refetches case data on language change; verify tokens are valid and
baseUrlis correct. - Receipt download or export failures: check platform file and share permissions (iOS share sheet, Android storage where applicable).
Development
cd packages/consent-sdk-flutter
flutter pub get
dart analyze
flutter test