Hashed Email (HEM) format

When you upload hashed-email identifiers via Upload a batch, the format has to match exactly what Vibe's matcher expects. Mismatches don't produce errors — they produce audiences that ingest cleanly but match a much smaller share of CTV profiles than expected.

Specification

Algorithm: SHA-256.

Normalization, in order, before hashing:

  1. Lowercase the entire address.
  2. Trim leading and trailing whitespace.
  3. Do not strip mailto: prefixes — reject those rows client-side instead.
  4. Keep Gmail dots and + suffixes as-is. Vibe matches the literal lowercased form. [verify with audiences owner]

Encoding: Lowercase hexadecimal. No salt.

Example

StepValue
Raw[email protected]
Normalized[email protected]
SHA-256 (hex)c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ffe

Reference implementations

Python

import hashlib

def to_hem(raw: str) -> str:
    normalized = raw.strip().lower()
    return hashlib.sha256(normalized.encode("utf-8")).hexdigest()

# to_hem("  [email protected]  ")
# -> "c0ffee..."

JavaScript / Node

import { createHash } from "crypto";

function toHem(raw) {
  return createHash("sha256")
    .update(raw.trim().toLowerCase())
    .digest("hex");
}

Validating before you upload

Vibe does not return an error on malformed hashes — they silently fail to match. Before sending production data, check three things:

  1. Every output is exactly 64 lowercase hex characters (/^[0-9a-f]{64}$/).
  2. Running your hasher against [email protected] produces the value in the table above.
  3. Hash a known address that exists in your test audience and confirm Vibe registers a match in the dashboard before scaling up.