> ## Documentation Index
> Fetch the complete documentation index at: https://docs.go.gbgplc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Consent Collection

export const ModuleToc = () => {
  const ref = useRef(null);
  const [items, setItems] = useState([]);
  const [inVariant, setInVariant] = useState(false);
  useEffect(() => {
    const toc = ref.current;
    if (!toc) return;
    const sections = [{
      label: "Capabilities",
      match: "capabilities",
      suffix: true
    }, {
      label: "Default outcomes",
      match: "default outcomes"
    }, {
      label: "Input payload",
      match: "input payload"
    }, {
      label: "Sample response",
      match: "sample response"
    }];
    const related = {
      label: "Related guides",
      match: "related guides and tutorials"
    };
    const selector = "h2[id], h3[id], h4[id], h5[id], h6[id]";
    const read = heading => ({
      heading,
      level: Number(heading.tagName.slice(1)),
      text: heading.textContent.replace(/​/g, "").trim()
    });
    const matches = (entry, section) => {
      const text = entry.text.toLowerCase();
      return text === section.match || section.suffix && text.endsWith(` ${section.match}`);
    };
    const isSection = entry => sections.some(section => matches(entry, section));
    const after = entry => toc.compareDocumentPosition(entry.heading) & Node.DOCUMENT_POSITION_FOLLOWING;
    const link = (entry, label) => ({
      label: label || entry.text,
      id: entry.heading.id
    });
    const sectionLinks = pool => sections.map(section => {
      const entry = pool.find(candidate => matches(candidate, section));
      return entry ? link(entry, section.label) : null;
    }).filter(Boolean);
    const root = toc.closest("#content-area") || document.body;
    const key = window.location.pathname;
    if (!root.moduleTocPage || root.moduleTocPage.key !== key) {
      root.moduleTocPage = {
        key,
        headings: Array.from(root.querySelectorAll(selector)).filter(heading => !heading.closest("details")).map(read)
      };
    }
    const page = root.moduleTocPage.headings.filter(after);
    const relatedEntry = page.find(entry => matches(entry, related));
    const relatedLink = relatedEntry ? [link(relatedEntry, related.label)] : [];
    const variant = toc.closest("details");
    if (variant) {
      const own = Array.from(variant.querySelectorAll(selector)).map(read).filter(after);
      const ownLinks = sectionLinks(own);
      const shared = sectionLinks(page).filter(fallback => !ownLinks.some(existing => existing.label === fallback.label));
      const order = sections.map(section => section.label);
      const merged = [...ownLinks, ...shared].sort((a, b) => order.indexOf(a.label) - order.indexOf(b.label));
      setInVariant(true);
      setItems([...merged, ...relatedLink]);
      return;
    }
    const isVariant = (entry, index) => {
      if (isSection(entry)) return false;
      for (const next of page.slice(index + 1)) {
        if (next.level <= entry.level) return false;
        if (isSection(next)) return true;
      }
      return false;
    };
    const loose = [];
    const groups = [];
    page.forEach((entry, index) => {
      if (isVariant(entry, index)) groups.push({
        entry,
        pool: []
      }); else if (isSection(entry)) (groups.length ? groups[groups.length - 1].pool : loose).push(entry);
    });
    const variants = groups.map(group => ({
      ...link(group.entry),
      children: sectionLinks(group.pool)
    })).filter(group => group.children.length);
    setInVariant(false);
    setItems(variants.length > 1 ? [...sectionLinks(loose), ...variants, ...relatedLink] : [...sectionLinks(page), ...relatedLink]);
  }, []);
  const linkClass = "block -ml-px border-l border-transparent pl-4 text-gray-600 dark:text-gray-400 hover:border-gray-400 hover:text-gray-900 dark:hover:text-gray-200";
  return <nav ref={ref} aria-label={inVariant ? "In this variant" : "On this page"} data-module-toc="" className="module-toc not-prose my-6 text-sm">
      {items.length > 0 && <p className="mb-2 font-medium text-gray-900 dark:text-gray-200">
          {inVariant ? "In this variant" : "On this page"}
        </p>}
      {items.length > 0 && <ul className="space-y-1.5 border-l border-gray-200 dark:border-white/10">
          {items.map(item => <li key={item.id}>
              <a href={`#${item.id}`} className={item.children ? `${linkClass} font-medium text-gray-800 dark:text-gray-200` : linkClass}>
                {item.label}
              </a>
              {item.children && <ul className="mt-1.5 space-y-1.5 pl-4">
                  {item.children.map(child => <li key={child.id}>
                      <a href={`#${child.id}`} className={linkClass}>
                        {child.label}
                      </a>
                    </li>)}
                </ul>}
            </li>)}
        </ul>}
    </nav>;
};

Presents a consent agreement to the end user and records their acceptance, producing an auditable record of what was consented to and when

This page documents the **Consent Collection** module, including its variants, capabilities, and the result values it returns.

<ModuleToc />

## Consent Collection: Consent Collection

Presents a consent agreement to the end user and records their acceptance, producing an auditable record of what was consented to and when. Use as a gate before any journey step that processes personal data.

### Capabilities

The module returns the following capabilities.

#### Consent given

True when the end user affirmatively accepted the consent agreement presented to them. False in every other case, including a timeout.

| Detail | Description |
| - | - |
| Type | Boolean |
| Default | `isFalse` |

#### Consent status

How the consent step concluded - accepted, actively declined, timed out, or never presented.

| Code | Label | Description |
| - | - | - |
| `ACCEPTED` | Accepted | The end user was presented with the agreement and explicitly accepted it. |
| `DECLINED` | Declined | The consent step was submitted but no explicit acceptance was recorded. |
| `TIMED_OUT` | Timed Out | The step timer fired before the end user submitted a response. |
| `NOT_PRESENTED` | Not Presented | The consent agreement was never rendered to the end user. |

### Default outcomes

The module is pre-configured with the following default outcomes, which can be used in evaluation and routing logic within the journey designer.

| Outcome | Condition | Description |
| - | - | - |
| `Consent Given` | Consent Given is `true` | The end user was presented with the consent agreement and explicitly accepted it. |
| `Error` | — | The module couldn't return a result. |
| `Consent Not Given` | default, when no conditions matched | The end user didn't accept the consent agreement, whether they declined it, the step timed out, or the agreement was never presented. |

### Input payload

The following is a sample payload used to submit data to the **Consent Collection** module for processing.

```json JSON theme={null}
{
  "context": {
    "subject": {
      "consent": [
        {
          "type": "explicit",
          "url": "https://www.gbgplc.com/legal/consent/identity-verification-v1",
          "terms": "I agree that GBG may process my personal data to verify my identity.",
          "effectiveDate": "2026-08-31T09:00:00Z"
        }
      ]
    }
  }
}
```

| Field | Required | Description |
| - | - | - |
| `Consent/url` | Yes | A link to the consent agreement presented to the end user, stored so the exact wording consented to can be retrieved later. |
| `Consent/terms` | No | The text of the consent agreement presented to the end user. |
| `Consent/effectiveDate` | No | The date and time the consent takes effect, in ISO 8601 format. |

### Sample response

The following is a sample response returned by the module.

```json JSON theme={null}
{
  "response": {
    "advice": {
      "consentGiven": true,
      "consentStatus": "ACCEPTED"
    },
    "outcome": "Consent Given"
  }
}
```
