# Example Flows This guide illustrates common integration patterns with sequence diagrams and code examples. > **Important:** Consent must always be collected before processing user data. This ensures compliance with privacy regulations and establishes a clear consent record. ## User Sign Up When a new user registers on your platform, collect consent first on a temporary ID, then create the user and link the consent. ### Sequence Diagram ```mermaid sequenceDiagram participant U as User participant F as Frontend participant B as Your Backend participant R as Redacto API Note over U,R: Phase 1: Collect Consent First U->>F: Fill registration form F->>F: Generate temporary ID (UUID) F->>B: Request token for temp ID B->>R: POST /tokens/generate (org_user_id: temp_xxx) R-->>B: JWT tokens B-->>F: Return tokens F->>F: Show RedactoNoticeConsent modal U->>F: Accept consent F->>R: Submit consent (via SDK) R-->>F: Consent recorded (user created) Note over U,R: Phase 2: Create User & Link F->>B: Submit registration with temp ID B->>B: Create user in your system B->>R: POST /consent-users/ (org_user_id: user_123) R-->>B: Primary user created B->>R: POST /consent-users/link-users Note right of B: primary: user_123
alias: temp_xxx R-->>B: Users linked B-->>F: Registration complete F-->>U: Redirect to dashboard ``` ### Backend Code ```typescript import express, { Request, Response } from "express"; import axios from "axios"; const app = express(); app.use(express.json()); const CMS_API_KEY = process.env.CMS_API_KEY; const REDACTO_BASE_URL = process.env.REDACTO_BASE_URL || "https://api.redacto.io"; const ORG_UUID = process.env.ORG_UUID; const WORKSPACE_UUID = process.env.WORKSPACE_UUID; // Step 1: Generate token for temporary ID (before consent) app.post("/api/consent/temp-token", async (req: Request, res: Response) => { const { temp_id } = req.body; if (!temp_id) { res.status(400).json({ error: "temp_id is required" }); return; } try { const response = await axios.post( `${REDACTO_BASE_URL}/consent/public/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/tokens/generate`, { org_user_id: temp_id }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); res.json({ token: response.data.detail.token, refresh_token: response.data.detail.refresh_token, }); } catch (error) { console.error("Error generating token:", error); res.status(500).json({ error: "Failed to generate token" }); } }); // Step 2: Complete registration after consent is given app.post("/api/register", async (req: Request, res: Response) => { const { email, password, name, temp_id } = req.body; try { // 1. Create user in your system const user = await createUser({ email, password, name }); // 2. Create primary consent user in Redacto await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/`, { org_user_id: user.id, primary_email: email, name: name, }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); // 3. Link temp consent user to the primary user if (temp_id) { await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/link-users`, { primary_org_user_id: user.id, alias_org_user_ids: [temp_id], }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); } // 4. Generate new tokens for the registered user const tokenResponse = await axios.post( `${REDACTO_BASE_URL}/consent/public/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/tokens/generate`, { org_user_id: user.id, email: email }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); res.json({ user: { id: user.id, email, name }, token: tokenResponse.data.detail.token, refresh_token: tokenResponse.data.detail.refresh_token, }); } catch (error) { console.error("Registration error:", error); res.status(500).json({ error: "Registration failed" }); } }); // Helper function (implement based on your auth system) async function createUser(data: { email: string; password: string; name: string }) { // Your user creation logic here return { id: `user_${Date.now()}`, ...data }; } app.listen(3000, () => console.log("Server running on port 3000")); ``` ### Frontend Code ```tsx import { RedactoNoticeConsent } from "@redacto.io/consent-sdk-react"; import { useState, useEffect } from "react"; import { v4 as uuidv4 } from "uuid"; const NOTICE_UUID = process.env.NEXT_PUBLIC_NOTICE_UUID; const REDACTO_BASE_URL = process.env.NEXT_PUBLIC_REDACTO_BASE_URL; interface FormData { email: string; password: string; name: string; } interface Tokens { token: string; refresh_token: string; } function SignUpPage() { const [tempId, setTempId] = useState(""); const [tokens, setTokens] = useState(null); const [showConsent, setShowConsent] = useState(false); const [formData, setFormData] = useState({ email: "", password: "", name: "" }); const [consentGiven, setConsentGiven] = useState(false); useEffect(() => { // Generate temporary ID on mount const id = `temp_${uuidv4()}`; setTempId(id); fetchTempToken(id); }, []); const fetchTempToken = async (id: string) => { const response = await fetch("/api/consent/temp-token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ temp_id: id }), }); const data = await response.json(); setTokens(data); setShowConsent(true); }; const handleConsentAccept = () => { setConsentGiven(true); setShowConsent(false); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const response = await fetch("/api/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...formData, temp_id: tempId }), }); if (response.ok) { // Registration complete, redirect to dashboard window.location.href = "/dashboard"; } }; return (
{/* Step 1: Show consent modal first */} {showConsent && tokens && ( { // User declined - cannot proceed with registration window.location.href = "/consent-required"; }} onError={(error) => console.error("Consent error:", error)} /> )} {/* Step 2: Show registration form after consent */} {consentGiven && (

Complete Your Registration

setFormData({ ...formData, email: e.target.value })} required /> setFormData({ ...formData, password: e.target.value })} required /> setFormData({ ...formData, name: e.target.value })} required />
)}
); } export default SignUpPage; ``` *** ## User Log In When an existing user logs in, authenticate them first, then let the SDK check if consent is valid or needs to be renewed. ### Sequence Diagram ```mermaid sequenceDiagram participant U as User participant F as Frontend participant B as Your Backend participant R as Redacto API U->>F: Enter credentials F->>B: POST /login B->>B: Authenticate user B->>R: POST /tokens/generate (org_user_id) R-->>B: JWT tokens B-->>F: Return user + tokens F->>F: Render RedactoNoticeConsent with validateAgainst Note over F,R: SDK checks consent status automatically alt Consent needed/expired F->>F: Show consent modal U->>F: Accept consent F->>R: Submit consent (via SDK) R-->>F: Consent recorded end F-->>U: Login complete ``` ### Backend Code ```typescript import express, { Request, Response } from "express"; import axios from "axios"; const app = express(); app.use(express.json()); const CMS_API_KEY = process.env.CMS_API_KEY; const REDACTO_BASE_URL = process.env.REDACTO_BASE_URL || "https://api.redacto.io"; const ORG_UUID = process.env.ORG_UUID; const WORKSPACE_UUID = process.env.WORKSPACE_UUID; app.post("/api/login", async (req: Request, res: Response) => { const { email, password } = req.body; try { // 1. Authenticate user in your system const user = await authenticateUser(email, password); if (!user) { res.status(401).json({ error: "Invalid credentials" }); return; } // 2. Generate tokens for the user const tokenResponse = await axios.post( `${REDACTO_BASE_URL}/consent/public/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/tokens/generate`, { org_user_id: user.id }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); res.json({ user: { id: user.id, email: user.email, name: user.name }, token: tokenResponse.data.detail.token, refresh_token: tokenResponse.data.detail.refresh_token, }); } catch (error) { console.error("Login error:", error); res.status(500).json({ error: "Login failed" }); } }); // Helper function (implement based on your auth system) async function authenticateUser(email: string, password: string) { // Your authentication logic here // Returns user object or null if invalid return { id: "user_123", email, name: "John Doe" }; } app.listen(3000, () => console.log("Server running on port 3000")); ``` ### Frontend Code ```tsx import { RedactoNoticeConsent } from "@redacto.io/consent-sdk-react"; import { useState } from "react"; const NOTICE_UUID = process.env.NEXT_PUBLIC_NOTICE_UUID; const REDACTO_BASE_URL = process.env.NEXT_PUBLIC_REDACTO_BASE_URL; interface Tokens { token: string; refresh_token: string; } interface User { id: string; email: string; name: string; } function LoginPage() { const [tokens, setTokens] = useState(null); const [user, setUser] = useState(null); const [showConsent, setShowConsent] = useState(false); const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const response = await fetch("/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: formData.get("email"), password: formData.get("password"), }), }); if (!response.ok) { alert("Login failed"); return; } const data = await response.json(); setUser(data.user); setTokens({ token: data.token, refresh_token: data.refresh_token }); setShowConsent(true); }; const handleConsentComplete = () => { // Consent accepted or already valid setShowConsent(false); window.location.href = "/dashboard"; }; return (

Log In

{/* SDK checks consent status and shows modal only if needed */} {showConsent && tokens && ( { // User declined - handle accordingly window.location.href = "/consent-required"; }} onError={(error) => { // If error is because consent already exists, proceed console.error("Consent error:", error); handleConsentComplete(); }} /> )}
); } export default LoginPage; ``` *** ## Consent on Anonymous IDs with Linking Collect consent from anonymous visitors, then link to their real identity when they register or log in. ### Sequence Diagram ```mermaid sequenceDiagram participant U as User participant F as Frontend participant B as Your Backend participant R as Redacto API Note over U,R: Phase 1: Anonymous Consent U->>F: Visit site (anonymous) F->>F: Generate anonymous ID (UUID) F->>B: Request token for anon ID B->>R: POST /tokens/generate (org_user_id: anon_xxx) R-->>B: JWT tokens B-->>F: Return tokens F->>F: Show RedactoNoticeConsentInline U->>F: Accept consent F->>R: Submit consent (via SDK) R-->>F: Consent recorded (anon user created) Note over U,R: Phase 2: User Registers U->>F: Fill registration form F->>B: Submit with anon ID B->>B: Create user in your system B->>R: POST /consent-users/ (org_user_id: user_123) R-->>B: User created B->>R: POST /consent-users/link-users Note right of B: primary: user_123
alias: anon_xxx R-->>B: Users linked B-->>F: Registration complete F-->>U: Continue with consent preserved ``` ### Backend Code ```typescript import express, { Request, Response } from "express"; import axios from "axios"; const app = express(); app.use(express.json()); const CMS_API_KEY = process.env.CMS_API_KEY; const REDACTO_BASE_URL = process.env.REDACTO_BASE_URL || "https://api.redacto.io"; const ORG_UUID = process.env.ORG_UUID; const WORKSPACE_UUID = process.env.WORKSPACE_UUID; // Generate token for anonymous user app.post("/api/consent/anonymous-token", async (req: Request, res: Response) => { const { anonymous_id } = req.body; if (!anonymous_id) { res.status(400).json({ error: "anonymous_id is required" }); return; } try { const response = await axios.post( `${REDACTO_BASE_URL}/consent/public/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/tokens/generate`, { org_user_id: anonymous_id }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); res.json({ token: response.data.detail.token, refresh_token: response.data.detail.refresh_token, }); } catch (error) { console.error("Error generating token:", error); res.status(500).json({ error: "Failed to generate token" }); } }); // Register user and link anonymous consent app.post("/api/register-with-anonymous", async (req: Request, res: Response) => { const { email, password, name, anonymous_id } = req.body; try { // 1. Create user in your system const user = await createUser({ email, password, name }); // 2. Create consent user in Redacto await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/`, { org_user_id: user.id, primary_email: email, name: name, }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); // 3. Link anonymous user to the new user if (anonymous_id) { await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/link-users`, { primary_org_user_id: user.id, alias_org_user_ids: [anonymous_id], }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); } // 4. Generate new tokens for the registered user const tokenResponse = await axios.post( `${REDACTO_BASE_URL}/consent/public/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/tokens/generate`, { org_user_id: user.id, email: email }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); res.json({ user: { id: user.id, email, name }, token: tokenResponse.data.detail.token, refresh_token: tokenResponse.data.detail.refresh_token, }); } catch (error) { console.error("Registration error:", error); res.status(500).json({ error: "Registration failed" }); } }); // Helper function async function createUser(data: { email: string; password: string; name: string }) { return { id: `user_${Date.now()}`, ...data }; } app.listen(3000, () => console.log("Server running on port 3000")); ``` ### Frontend Code ```tsx import { RedactoNoticeConsentInline } from "@redacto.io/consent-sdk-react"; import { useState, useEffect } from "react"; import { v4 as uuidv4 } from "uuid"; const ORG_UUID = process.env.NEXT_PUBLIC_ORG_UUID; const WORKSPACE_UUID = process.env.NEXT_PUBLIC_WORKSPACE_UUID; const NOTICE_UUID = process.env.NEXT_PUBLIC_NOTICE_UUID; const REDACTO_BASE_URL = process.env.NEXT_PUBLIC_REDACTO_BASE_URL; interface Tokens { accessToken: string | undefined; refreshToken: string | undefined; } function LandingPage() { const [anonymousId, setAnonymousId] = useState(""); const [tokens, setTokens] = useState({ accessToken: undefined, refreshToken: undefined }); const [consentGiven, setConsentGiven] = useState(false); useEffect(() => { // Generate or retrieve anonymous ID let anonId = localStorage.getItem("anon_consent_id"); if (!anonId) { anonId = `anon_${uuidv4()}`; localStorage.setItem("anon_consent_id", anonId); } setAnonymousId(anonId); fetchAnonymousToken(anonId); }, []); const fetchAnonymousToken = async (anonId: string) => { const response = await fetch("/api/consent/anonymous-token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ anonymous_id: anonId }), }); const data = await response.json(); setTokens({ accessToken: data.token, refreshToken: data.refresh_token }); }; const handleRegister = async (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); const response = await fetch("/api/register-with-anonymous", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: formData.get("email"), password: formData.get("password"), name: formData.get("name"), anonymous_id: anonymousId, }), }); if (response.ok) { // Anonymous consent is now linked to the user localStorage.removeItem("anon_consent_id"); window.location.href = "/dashboard"; } }; return (

Welcome!

{/* Inline consent for anonymous users */} setConsentGiven(valid)} onAccept={() => console.log("Anonymous consent recorded")} onError={(error) => console.error("Consent error:", error)} /> {/* Show registration form after consent */} {consentGiven && (

Create an account to continue

)}
); } export default LandingPage; ``` *** ## Consent from Multiple Channels with Linking When users interact with your platform through different channels (web, mobile app, email), consolidate their consent records. Note that consent users are created when consent is submitted, not when tokens are generated. ### Sequence Diagram ```mermaid sequenceDiagram participant W as Web User participant M as Mobile User participant B as Your Backend participant R as Redacto API Note over W,R: Channel 1: Web (Email identifier) W->>B: Consent via web form B->>R: POST /tokens/generate (email: user@example.com) R-->>B: Tokens W->>R: Submit consent via SDK R-->>W: Consent recorded (user created with email as org_user_id) Note over M,R: Channel 2: Mobile App (Phone identifier) M->>B: Consent via mobile app B->>R: POST /tokens/generate (mobile: +1234567890) R-->>B: Tokens M->>R: Submit consent via SDK R-->>M: Consent recorded (user created with phone as org_user_id) Note over B,R: Channel 3: User identified as same person B->>B: Detect same user across channels B->>R: POST /consent-users/ (org_user_id: customer_123) R-->>B: Primary user created B->>R: POST /consent-users/link-users Note right of B: primary: customer_123
aliases: [email, phone] R-->>B: All consent consolidated ``` ### Backend Code ```typescript import express, { Request, Response } from "express"; import axios, { AxiosError } from "axios"; const app = express(); app.use(express.json()); const CMS_API_KEY = process.env.CMS_API_KEY; const REDACTO_BASE_URL = process.env.REDACTO_BASE_URL || "https://api.redacto.io"; const ORG_UUID = process.env.ORG_UUID; const WORKSPACE_UUID = process.env.WORKSPACE_UUID; interface LinkResult { primary_user_uuid: string; linked: string[]; already_linked: string[]; not_found: string[]; conflicts: Array<{ org_user_id: string; existing_primary_org_user_id: string; }>; } // When user is identified across channels app.post("/api/consolidate-user", async (req: Request, res: Response) => { const { user_id, email, phone } = req.body; try { // 1. Create the primary consent user with your system's ID try { await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/`, { org_user_id: user_id, primary_email: email, primary_mobile: phone, }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); } catch (error) { const axiosError = error as AxiosError; // Handle 409 if user already exists - that's fine if (axiosError.response?.status !== 409) { throw error; } } // 2. Build list of potential alias identifiers // These are the org_user_ids that were created when consent was submitted const aliasIds: string[] = []; // When consent is submitted with just email, the email becomes the org_user_id if (email) { aliasIds.push(email); } // When consent is submitted with just phone, the phone becomes the org_user_id if (phone) { aliasIds.push(phone); } // 3. Link all aliases to the primary user let linkResult: LinkResult | null = null; if (aliasIds.length > 0) { const linkResponse = await axios.post( `${REDACTO_BASE_URL}/consent/organisations/${ORG_UUID}/workspaces/${WORKSPACE_UUID}/consent-ledger/consent-users/link-users`, { primary_org_user_id: user_id, alias_org_user_ids: aliasIds, }, { headers: { "X-CMS-API-Key": CMS_API_KEY, "Content-Type": "application/json", }, } ); linkResult = linkResponse.data.detail; console.log("Link results:", linkResult); } res.json({ success: true, link_result: linkResult, }); } catch (error) { console.error("Consolidation error:", error); res.status(500).json({ error: "Failed to consolidate user" }); } }); app.listen(3000, () => console.log("Server running on port 3000")); ``` ### Handling Link Results ```typescript interface LinkResult { primary_user_uuid: string; linked: string[]; already_linked: string[]; not_found: string[]; conflicts: Array<{ org_user_id: string; existing_primary_org_user_id: string; }>; } async function handleLinkResults(result: LinkResult): Promise { // Successfully linked if (result.linked.length > 0) { console.log(`Linked ${result.linked.length} users`); } // Already linked (idempotent, no action needed) if (result.already_linked.length > 0) { console.log(`${result.already_linked.length} already linked`); } // Not found - these users haven't given consent yet // (consent submission creates users, not token generation) if (result.not_found.length > 0) { console.log(`${result.not_found.length} not found - no consent submitted yet`); // Store for later linking when they do submit consent await storeForLaterLinking(result.not_found); } // Conflicts - already linked to another primary if (result.conflicts.length > 0) { // May need manual review or different resolution strategy for (const conflict of result.conflicts) { console.warn( `${conflict.org_user_id} already linked to ${conflict.existing_primary_org_user_id}` ); } } } async function storeForLaterLinking(identifiers: string[]): Promise { // Store identifiers to retry linking later // Implementation depends on your system } ``` ### Use Case: CRM Integration ```typescript interface CRMCustomer { crm_id: string; email?: string; phone?: string; web_session_id?: string; app_device_id?: string; } // When CRM identifies a customer across touchpoints async function syncCustomerConsent(crmCustomer: CRMCustomer): Promise { const identifiers: string[] = []; // Collect all known identifiers that may have been used for consent if (crmCustomer.email) identifiers.push(crmCustomer.email); if (crmCustomer.phone) identifiers.push(crmCustomer.phone); if (crmCustomer.web_session_id) identifiers.push(crmCustomer.web_session_id); if (crmCustomer.app_device_id) identifiers.push(crmCustomer.app_device_id); // Create primary user with CRM ID await createConsentUser({ org_user_id: crmCustomer.crm_id, primary_email: crmCustomer.email, primary_mobile: crmCustomer.phone, metadata: { source: "crm_sync" }, }); // Link all identifiers const result = await linkUsers({ primary_org_user_id: crmCustomer.crm_id, alias_org_user_ids: identifiers, }); // Handle results await handleLinkResults(result); // Now all consent from any channel is consolidated } // Helper functions (implement based on your setup) async function createConsentUser(data: { org_user_id: string; primary_email?: string; primary_mobile?: string; metadata?: Record; }): Promise { // Call Redacto API to create user } async function linkUsers(data: { primary_org_user_id: string; alias_org_user_ids: string[]; }): Promise { // Call Redacto API to link users return {} as LinkResult; } ```