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

# How it works

> The hashing, check and report flow, and the life of a registry entry

This page explains what happens to an email from the moment a member bans a user to the moment the
entry is deleted. For the request and response details, see [Check a sign-up](/registry/checking)
and [Report a banned user](/registry/reporting).

## Terms

| Term | Meaning |
| - | - |
| Member | A platform with an active registry membership. It checks sign-ups and can report banned users. |
| User | A person with an account on a member platform. |
| Identifier | A value that identifies a user. The registry accepts an email address. |
| Entry | One member's report that one identifier belongs to a user it banned, for one category. |
| Signal | One category in a check result, aggregated across every member that reported the identifier. |

## Hashing

An email passes through three steps before the registry stores anything. The first two run on your
servers, so the email itself never leaves your platform.

```mermaid theme={null}
flowchart TB
  subgraph platform["On your platform"]
    A["Email at sign-up<br/>J.a.n.e.D.o.e+x@GoogleMail.com"] -->|"1. Normalise"| B["janedoe@gmail.com"]
    B -->|"2. SHA-256"| C["d6117306...656c69"]
  end
  subgraph registry["In the Shared Registry"]
    D["Stored identifier<br/>HMAC with the Omnifence secret key"]
  end
  C -->|"3. Keyed HMAC"| D
```

1. **Normalise.** Different spellings of one mailbox become one value. For example, Gmail ignores
   dots and `+tags`, so `J.a.n.e.D.o.e+x@GoogleMail.com` and `janedoe@gmail.com` reach the same
   inbox. Every member applies the same [normalisation rules](/registry/hashing).
2. **SHA-256.** You hash the normalised email and send only the hash.
3. **Keyed HMAC.** The registry computes an HMAC of your hash with a secret key that only Omnifence
   holds, and stores that value. It discards your hash when the request ends.

Step 3 matters because a plain SHA-256 of an email is easy to reverse: anyone with a list of email
addresses can hash every one and compare. Without the secret key, the stored values match nothing.
See [Privacy and security](/registry/privacy-and-security).

## Reporting and checking

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant A as Member A
  participant R as Shared Registry
  participant B as Member B
  Note over A: A person reviews the evidence<br/>and upholds a ban
  A->>R: Report: hash + category + case reference
  R-->>A: 201 Created (entry)
  Note over B: Someone signs up<br/>with the same email
  B->>R: Check: hash + context "signup"
  R-->>B: match: true, signals per category
  Note over B: Member B decides under its own terms
```

The same hash from two members leads to the same stored identifier, so Member B matches the report
from Member A without either member ever seeing the other's data.

## What a check returns

A check returns one **signal** for each category that at least one member reported for the
identifiers you sent:

| Field | Meaning |
| - | - |
| `category` | The category of harm. |
| `reporter_count` | How many different members reported the user in this category. |
| `first_reported_at` | When the earliest of those reports was made. |
| `last_reported_at` | When the most recent report was made or refreshed. |
| `automated_refusal_permitted` | Whether you can refuse on the match alone. See [Categories](/registry/categories). |

A check never returns an entry ID, the name of a reporting member, or a case reference. A signal is
the same whether one member reported the user or five, except for `reporter_count`.

Only live entries match. An entry that is disputed, revoked or past its retention period does not
appear in any check.

## The life of an entry

```mermaid theme={null}
stateDiagram-v2
  direction LR
  [*] --> active: Member reports
  active --> disputed: Person disputes
  disputed --> active: Report upheld
  active --> revoked: Member revokes, or dispute not upheld
  active --> expired: Retention ends
  revoked --> [*]: Deleted after 30 days
  expired --> [*]: Deleted after 30 days
```

| Status | Matches | What it means |
| - | - | - |
| `active` | Yes | The report is live. |
| `disputed` | No | The person named disputed the report. It does not match until Omnifence resolves the dispute. |
| `revoked` | No | The member withdrew the report, or a dispute found the report was not supported. |
| `expired` | No | The retention period for the category ended. |

The diagram shows the main path. A disputed entry can also be revoked or expire, and a new report
from the member reactivates a revoked or expired entry (see below).

A revoked or expired entry is deleted 30 days after it stops matching. After that, nothing in the
registry links to the identifier.

When a member reports the same identifier and category again, the registry refreshes the existing
entry: it takes the new case reference and restarts the retention period. A new report reactivates a
revoked or expired entry, because a new ban is a new decision. It never ends a dispute: only
Omnifence resolves a dispute.

## Several members, one user

Each member's report is a separate entry. If three members report the same user for payment fraud,
the registry holds three entries, and a check returns one signal with `reporter_count: 3`. If one of
those members revokes its report, the signal drops to `reporter_count: 2`. The other two reports are
not affected.

## Next steps

<Columns cols={2}>
  <Card title="Hash an email" icon="hashtag" href="/registry/hashing">
    The normalisation rules, a reference implementation and test vectors.
  </Card>

  <Card title="Privacy and security" icon="shield-halved" href="/registry/privacy-and-security">
    What the registry stores, what it never stores, and who can see what.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.