Skip to main content

Slack Revenue Alerts — Data Flow & SOC 2 Posture

Modules:
  • Silvia-Web/lib/slack-revenue.ts — per-event alerts fired from the Stripe webhook handler
  • Silvia-Admin/lib/slack-digest.ts — recurring 14:05 UTC daily pulse + Monday 15:00 UTC weekly digest
Why the digest lives in Silvia-Admin: Silvia-Web is at its Vercel cron-slot ceiling. Both repos run against the same Supabase project, so the digest crons access the same stripe_subscriptions / stripe_subscription_history / all_users / ai_token_usage_daily tables and the same get_user_token_usage_in_window / get_cap_crossings_in_range RPCs that the per-event alerts use — the two surfaces remain in numerical agreement by construction. The Silvia-Admin digest module sends the same data classes to the same Slack channel under the same SOC 2 control SLACK-REV-001; nothing about the data-minimization posture changes from this split.
Subprocessor: Slack (data sent via Incoming Webhook to a private channel) Channel: #silvia-revenue (same channel for both surfaces; distinct visual treatment) Audience: Named ops members only This document describes the data we send to Slack from the revenue-alerts modules, why we send it, and the SOC 2 Type 2 controls in place.

What we send

Every alert contains exactly these classes of data:

What we deliberately do NOT send

SOC 2 Type 2 control description

Control name: SLACK-REV-001 — Data minimization for revenue notifications Stated control: The Slack revenue notifications module sends only pseudonymous internal identifiers and aggregate business metrics to the configured Slack webhook. Direct PII (names, emails, phone numbers) and externally-resolvable identifiers (Stripe customer IDs) are excluded by design and verified at code-review time. Operating evidence:
  1. Code review: lib/slack-revenue.ts imports a fixed allowlist of non-PII helpers from ./token-limit (priceIdToTier, tierToPriceId, calendarMonthBounds) and ./token-limit-client (TIER_LIMITS, TIER_NAMES, TIER_PRICES_USD, formatTokens, and the Tier type). It does NOT import decryptFirstName, decryptEmail, or any other decryption function. The rule the reviewer enforces is “no module that returns plaintext from an encrypted PII column may be imported here” — adding any new helper that does NOT match that rule is the only thing requiring this enumeration to update. Pull request review enforces this.
  2. Database column allowlist: The getUserContext helper selects only created_at from all_users. It does not select first_name, email, phone_number, or any other PII column. It also reads non-PII subscription metadata (created_at / stripe_subscription_id from stripe_subscription_history; price_id / created_at from stripe_subscriptions) to derive prior-paid tenure and tier for the alert’s “Lifecycle” field — timestamps and a Stripe subscription / price identifier, none of which are PII.
  3. Per-event field allowlist: The buildEventFields switch emits only Clerk ID, tier, price, dates, and aggregate counters. It does not reference Stripe customer ID from the event payload.
  4. Dedup table audit: The slack_revenue_alerts_sent table is the only persistent record of “what alerts fired and when.” Queryable for audit purposes.
  5. Channel access control: SLACK_REVENUE_WEBHOOK_URL points at a private Slack channel with named, restricted membership. Slack itself is covered by our Data Processing Agreement and listed as a subprocessor in the privacy policy.

Escalation path

If the SOC 2 auditor determines that even the Clerk ID is too identifying to send to a third-party SaaS, the next step is:
  • HMAC-SHA256 the Clerk ID with a server-side secret key (HMAC takes a confidential cryptographic key per NIST FIPS 198-1 / RFC 2104; the key must be kept secret, unlike a salt which is a non-secret password-hashing primitive)
  • Truncate to 8-12 hex chars
  • Post the truncated hash instead of the raw Clerk ID
  • Build an admin tool that accepts a hash and returns the Clerk ID (or the user record) so ops can still act on alerts
This is not implemented today. The agreed-upon trigger for the work is an auditor recommendation in writing.

Change log