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

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<br/>alias: temp_xxx
    R-->>B: Users linked
    B-->>F: Registration complete
    F-->>U: Redirect to dashboard

Backend Code

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

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<string>("");
  const [tokens, setTokens] = useState<Tokens | null>(null);
  const [showConsent, setShowConsent] = useState(false);
  const [formData, setFormData] = useState<FormData>({ 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 (
    <div>
      {/* Step 1: Show consent modal first */}
      {showConsent && tokens && (
        <RedactoNoticeConsent
          noticeId={NOTICE_UUID!}
          accessToken={tokens.token}
          refreshToken={tokens.refresh_token}
          baseUrl={REDACTO_BASE_URL}
          onAccept={handleConsentAccept}
          onDecline={() => {
            // 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 && (
        <form onSubmit={handleSubmit}>
          <h1>Complete Your Registration</h1>
          <input
            type="email"
            placeholder="Email"
            value={formData.email}
            onChange={(e) => setFormData({ ...formData, email: e.target.value })}
            required
          />
          <input
            type="password"
            placeholder="Password"
            value={formData.password}
            onChange={(e) => setFormData({ ...formData, password: e.target.value })}
            required
          />
          <input
            type="text"
            placeholder="Full Name"
            value={formData.name}
            onChange={(e) => setFormData({ ...formData, name: e.target.value })}
            required
          />
          <button type="submit">Create Account</button>
        </form>
      )}
    </div>
  );
}

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

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

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

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<Tokens | null>(null);
  const [user, setUser] = useState<User | null>(null);
  const [showConsent, setShowConsent] = useState(false);

  const handleLogin = async (e: React.FormEvent<HTMLFormElement>) => {
    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 (
    <div>
      <form onSubmit={handleLogin}>
        <h1>Log In</h1>
        <input name="email" type="email" placeholder="Email" required />
        <input name="password" type="password" placeholder="Password" required />
        <button type="submit">Log In</button>
      </form>

      {/* SDK checks consent status and shows modal only if needed */}
      {showConsent && tokens && (
        <RedactoNoticeConsent
          noticeId={NOTICE_UUID!}
          accessToken={tokens.token}
          refreshToken={tokens.refresh_token}
          baseUrl={REDACTO_BASE_URL}
          validateAgainst="required"
          onAccept={handleConsentComplete}
          onDecline={() => {
            // 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();
          }}
        />
      )}
    </div>
  );
}

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

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<br/>alias: anon_xxx
    R-->>B: Users linked
    B-->>F: Registration complete
    F-->>U: Continue with consent preserved

Backend Code

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

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<Tokens>({ 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<HTMLFormElement>) => {
    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 (
    <div>
      <h1>Welcome!</h1>

      {/* Inline consent for anonymous users */}
      <RedactoNoticeConsentInline
        org_uuid={ORG_UUID!}
        workspace_uuid={WORKSPACE_UUID!}
        notice_uuid={NOTICE_UUID!}
        accessToken={tokens.accessToken}
        refreshToken={tokens.refreshToken}
        baseUrl={REDACTO_BASE_URL}
        onValidationChange={(valid) => setConsentGiven(valid)}
        onAccept={() => console.log("Anonymous consent recorded")}
        onError={(error) => console.error("Consent error:", error)}
      />

      {/* Show registration form after consent */}
      {consentGiven && (
        <form onSubmit={handleRegister}>
          <h2>Create an account to continue</h2>
          <input name="email" type="email" placeholder="Email" required />
          <input name="password" type="password" placeholder="Password" required />
          <input name="name" type="text" placeholder="Name" required />
          <button type="submit">Create Account</button>
        </form>
      )}
    </div>
  );
}

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

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: [email protected])
    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<br/>aliases: [email, phone]
    R-->>B: All consent consolidated

Backend Code

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

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<void> {
  // 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<void> {
  // Store identifiers to retry linking later
  // Implementation depends on your system
}

Use Case: CRM Integration

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<void> {
  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<string, unknown>;
}): Promise<void> {
  // Call Redacto API to create user
}

async function linkUsers(data: {
  primary_org_user_id: string;
  alias_org_user_ids: string[];
}): Promise<LinkResult> {
  // Call Redacto API to link users
  return {} as LinkResult;
}

What’s Next