Module: Tracking::Hashing
- Defined in:
- app/lib/tracking/hashing.rb
Overview
SHA-256 PII hashing helpers shared by every ad-platform CAPI reporter.
Every server-side conversion API (Pinterest CAPI, OpenAI Ads CAPI,
Facebook CAPI, TikTok Events, Google enhanced conversions) requires
personally-identifying user fields to be hashed before transmission:
email lowercased + trimmed → SHA-256, phone normalized to E.164 →
SHA-256, and so on. Each reporter used to re-implement these helpers
([Pinterest::ConversionReporter#normalize_and_hash_email], the
private methods on [Invoicing::GoogleConversionReporter]); this
module centralizes them.
Google is a special case — it requires stripping dots from the
local-part of gmail.com / googlemail.com addresses before hashing
(because Google treats john.doe@gmail.com and johndoe@gmail.com
as the same account). Every other CAPI uses the industry-standard
plain lowercase+trim. We expose both variants explicitly so the
difference is obvious at the call site.
Class Method Summary collapse
-
.email(addr) ⇒ String?
SHA-256 hash of a lowercased, whitespace-trimmed email address with gmail-style dot-stripping on the local-part of
@gmail.comand@googlemail.comaddresses (Google treatsj.o.h.n@gmail.comandjohn@gmail.comas the same account). -
.phone(raw) ⇒ String?
SHA-256 hash of an E.164-normalized phone number.
-
.sha256(str) ⇒ String?
SHA-256 hash of a plain string — used for free-form identifiers like a customer's internal numeric ID that some platforms accept in hashed form even though it isn't PII per se.
Class Method Details
.email(addr) ⇒ String?
SHA-256 hash of a lowercased, whitespace-trimmed email address with
gmail-style dot-stripping on the local-part of @gmail.com and
@googlemail.com addresses (Google treats j.o.h.n@gmail.com and
john@gmail.com as the same account). Non-gmail addresses pass
through unchanged.
Used by every CAPI reporter in this codebase — the existing
Pinterest::ConversionReporter and Invoicing::GoogleConversionReporter
both did gmail dot-stripping pre-extraction, and tighter match rates on
gmail traffic are worth more than spec-purity for the few platforms
that nominally don't require it.
40 41 42 43 44 45 46 |
# File 'app/lib/tracking/hashing.rb', line 40 def email(addr) return nil if addr.blank? parts = addr.downcase.strip.split('@') parts[0] = parts[0].delete('.') if parts.last&.match?(/\A(gmail|googlemail)\.com\z/) Digest::SHA256.hexdigest(parts.join('@')) end |
.phone(raw) ⇒ String?
SHA-256 hash of an E.164-normalized phone number. Uses the
phonelib gem (already in the Gemfile for the order-shipping
phone validation path) to do the normalization; returns nil for
any input that can't be parsed into a plausible international
number.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'app/lib/tracking/hashing.rb', line 57 def phone(raw) return nil if raw.blank? # Phonelib is `require: false` in the Gemfile (heavy load cost, only # paid by the few call sites that actually parse phones). Require # lazily so this module stays cheap to load. require 'phonelib' parsed = ::Phonelib.parse(raw) # `parsed.e164` echoes garbage back for unparseable input — and with # `Phonelib.vanity_conversion` enabled app-wide (see the 220_phonelib # initializer), letter strings get keypad-mapped to digits # ("not-a-phone" → "+668274663"). Gate on `possible?` so only numbers # of a plausible length are hashed. return nil unless parsed.possible? Digest::SHA256.hexdigest(parsed.e164) end |
.sha256(str) ⇒ String?
SHA-256 hash of a plain string — used for free-form identifiers
like a customer's internal numeric ID that some platforms accept
in hashed form even though it isn't PII per se.
82 83 84 85 86 |
# File 'app/lib/tracking/hashing.rb', line 82 def sha256(str) return nil if str.blank? Digest::SHA256.hexdigest(str.to_s.downcase.strip) end |