Legal
Privacy posture
Last updated: 2026-05-17
This document is the operational complement to our privacy policy. The privacy policy says what we MAY legally do with data; this page says what we ACTUALLY do, what tooling enforces it, and how we would answer "did Amargi staff look at my data" with specifics.
It is published, customer-readable, and binding on us. If we ever deviate from a commitment here, the deviation is a P0: we fix it, log it, and tell affected customers.
TL;DR
- We do NOT browse customer message contents. The platform admin panel shows only counts and metadata. There is no "view this message" button.
- Operator access to message bodies happens only when an audit record explains why: abuse investigation, customer-opened support ticket, or lawful order. Every such access is logged.
- We do NOT sell data, do NOT share it with third parties for advertising, and do NOT train models on customer messages or on end-customer messages.
1. Authentication architecture (Amargi Hub SSO)
Amargi is a product family (Reach, Mail, Agents, Meet, Escalate). All products share a single identity provider called Amargi Hub for sign-in. Hub stores ONLY:
- Account credentials (email, password hash, 2FA secrets).
- Active sessions and refresh tokens.
- Organization membership and roles (who can see what inside each product).
Hub does NOT store any WhatsApp message data, contact lists, or any content governed by Meta's Business Solution Provider (BSP) terms. All of that is owned and processed exclusively by Reach. The boundary is clean by data type:
| Lives in Hub | Lives in Reach |
|---|---|
| Account identity (email, password hash, 2FA) | WhatsApp Business Accounts (WABAs) |
| Sessions, refresh tokens | Phone numbers, quality ratings, message tiers |
| Org membership, roles | Templates, campaigns, audiences |
| Workspace metadata | Conversations, messages, contacts, consent records |
| Cross-product navigation | Webhook subscriptions, outbound delivery state |
This pattern mirrors Twilio, MessageBird, 360dialog, and Infobip: the verified Meta Business entity is the same regardless of which product the customer uses. Hub is NOT a subprocessor and NOT a third party: it is an internal authentication service operated by Amargi Creative — same legal entity, same hosting, same compliance perimeter.
2. What an Amargi operator can see
The Amargi platform admin panel (route group (platform), gated on the platform_admin JWT claim) exposes the following per customer organization:
| Surface | What it shows |
|---|---|
/platform | Org count, user count, WABA count, MRR, aggregate message counts (last 7 / 30 days), outbox + webhook delivery stats. |
/platform/orgs | Org name, slug, plan, user count, WABA count, message count last 30 days, created date. |
/platform/orgs/{id} | Above plus: users with role + last-login; WABAs with provider id, region, verification status, phone-number count; usage + cost rollups; suspend / unsuspend controls. |
/platform/users | Email, name, verified flag, 2FA flag, platform-admin flag, org count, last-login timestamp. |
/platform/wabas | Provider WABA id, owning org, region, verification, active flag, phone-number count. |
/platform/phone-numbers | E.164, display name, owning org, status, quality rating, MPS throughput. Filterable by quality. |
/platform/templates | Template name, owning org, language, category, status, status reason. |
/platform/campaigns | Campaign name, owning org, status, queued / sent / delivered / failed counts. |
/platform/health | Outbox pending / dispatched / failed; webhook delivered / pending / parked; phone-number quality breakdown. |
3. What an Amargi operator does NOT see (in the panel)
- Message bodies, text, captions, document filenames, button payloads. Not in any DTO returned to the operator panel.
- Conversation transcripts between a customer and their end customers.
- Recipient phone numbers for individual messages (only the count per campaign).
- Contact lists, contact attributes, audience definitions beyond aggregate sizes.
- Webhook payload bodies, only delivery status counters.
- Stripe card details, never seen, held by Stripe.
4. When operator access to message contents is allowed
Three cases. Every one is logged.
- Abuse investigation. Meta has flagged a customer's WABA, or we have received a report of spam / scam / prohibited content. The operator opens the inspection tool, supplies a reason that references the abuse case, and reviews the minimum necessary message bodies to corroborate or refute the report. We are contractually obligated to do this under Meta's BSP terms; we cannot opt out.
- Support ticket the customer opened with us. The customer has asked us to look at message X for them ("why didn't this render?"). The operator's audit record references the ticket number.
- Lawful order, subpoena, court order, or equivalent in any jurisdiction the customer or their end-customers reside in. We require the order in writing, scope our response to the order's actual scope, and notify the affected customer except where prohibited by law.
We do NOT access message contents for product analytics, model training, marketing, sales prospecting, or curiosity. Doing so is grounds for termination of the operator's employment and for notifying affected customers and regulators if the access cannot be justified after the fact.
5. How the access log works
Every action under [PlatformAdmin] writes a row to platform.audit_events:
actor_user_id— The Amargi staff member who took the action.actor_email— Denormalized for human-readable display.action— Controller.Method, e.g. PlatformOrgs.Suspend.target_type, target_id, target_org_id— The record acted on.ip, user_agent, status_code— Request context (failed access attempts are visible too).reason— Free text. Required for sensitive actions.occurred_at— UTC timestamp.
The log is append-only at the application layer (no UPDATE or DELETE paths in the codebase) and visible to operators in /platform/audit-log. Customer-facing access ("show me every operator action that touched my org") is on the roadmap. We retain audit records for at least 7 years.
6. Encryption posture
Documented in detail so Meta reviewers, enterprise procurement teams, and security auditors can verify the controls without reading source.
6.1 In transit
- TLS 1.2 minimum on every public endpoint. TLS 1.3 preferred and default on supported clients.
- HSTS (max-age=31536000; includeSubDomains; preload) on every customer-facing domain.
- mTLS on internal service-to-service traffic where the platform spans multiple hosts.
- Webhook signature verification (HMAC-SHA256) on every inbound Meta webhook before any business logic runs. Outbound webhooks to customer subscribers are HMAC-signed with a per-subscription secret that the customer rotates from the UI.
6.2 At rest — layered controls
We use a defense-in-depth approach: three independent layers protect customer messaging data on the wire to disk.
- Disk-level full-volume encryption. All Postgres volumes are encrypted at rest by the cloud provider (LUKS-equivalent / KMS-managed AES-256). Database backups (snapshots + WAL archive) are encrypted with provider keys. Defends against physical disk theft and raw block storage exfiltration.
- Application-level envelope encryption on secrets. Sensitive credential fields — WABA access tokens, Page Access Tokens, Instagram tokens, webhook signing secrets — are written through ASP.NET Core's IDataProtector API. Each cipher-text row carries an application-issued key id; the master key lives in the secret backend (AWS Secrets Manager / GCP Secret Manager / HashiCorp Vault) and is rotated quarterly. A database dump on its own does not yield usable tokens.
- pgcrypto available for column-level encryption of message bodies on a per-org opt-in. The extension is declared in migrations and pgp_sym_encrypt / pgp_sym_decrypt helpers are wired behind the Reach:Privacy:EncryptMessageBodies flag. This is the strongest tier but reduces query flexibility (substring search stops working) and is OFF by default. Enterprise customers who require it can flip the flag on for their org.
6.3 What we deliberately do NOT do
- Customer-managed keys (BYOK / CMK). Planned as an enterprise feature; not yet offered. Until then, Amargi is in the trust boundary for at-rest data and this document is the commitment that bounds what we do with that trust.
- Encrypt every column. A WhatsApp conversation is by nature a log of customer / agent text — encrypting every row breaks the inbox search box. We encrypt the secrets that, if leaked, would let an attacker impersonate the customer's business to Meta; we do NOT encrypt every text-body row by default. This is the same posture every major WhatsApp BSP takes; see Twilio's, MessageBird's, and 360dialog's public security pages.
6.4 Key rotation cadence
- Master encryption key (envelope key for DataProtector): quarterly.
- Webhook signing secrets: customer-controlled, recommended quarterly.
- WABA access tokens: Meta-controlled lifecycle; we re-acquire on expiry via the BSP refresh path.
- Internal mTLS certificates: 90-day Let's Encrypt rotation.
6.5 How a reviewer can verify
- messaging.wabas.access_token_ref in a fresh database dump shows cipher-text, not plaintext. Confirm by inspecting any row.
- Reach:Secrets:Mode env var on prod must be set to a non-"passthrough" value — aws, gcp, or vault. ("passthrough" is dev-only and the API logs a startup warning when used outside development.)
- All inbound + outbound webhook handlers terminate with a 401 if the signature is missing or fails the constant-time HMAC compare. See the Webhooks integration-test suite for the negative-path coverage.
7. Tenant isolation
Every entity owned by an organization in the domain layer carries the ITenantOwned marker. EF Core query filters are automatically added to every query to constrain reads to the caller request's active organization. This prevents cross-tenant data leakage structurally, not just by convention, regardless of who signs in or via which product. Code that tries to query without the tenant filter breaks a unit test in the repository layer.
8. Data residency
Production data is stored in a single region selected per customer at onboarding. Default is eu-central-1. Cross-region replication, if any, stays inside the same legal jurisdiction.
9. Subprocessors
The complete list (with purpose, location, DPA link for each) is published at the subprocessors page. We notify customers of new subprocessors at least 30 days in advance.
10. Changes to this document
The current revision is committed to the repository at apps/reach/docs/PRIVACY-POSTURE.md and mirrored here. The git history of the repository file is the canonical change log. Material changes are announced to customers by email at least 30 days before they take effect.
11. Contact
Privacy questions, access requests, deletion requests: contact@amargicreative.com. Security issues: contact@amargicreative.com.