Security
Plain-English summary of how FixAdSpend handles your credentials, your ad-account data, and incident response. Procurement folks: the full version of any section below is available on request to admin@fixadspend.ai.
Credentials
OAuth refresh tokens (Google Ads, Meta Ads, Shopify) are stored encrypted at rest with AES-256-GCM, keyed off a 32-byte TOKEN_ENCRYPTION_KEY that lives only in Vercel's encrypted environment variables and is never checked into git. Decryption only happens at the moment a sync request is fulfilled.
Stripe webhook events are verified with HMAC-SHA256 against the Stripe-supplied STRIPE_WEBHOOK_SECRETbefore any handler logic runs.
Passwords (when the user signs up with email/password rather than OAuth) are bcrypt-hashed at 12 rounds via bcryptjs.
OAuth scope minimization
We request the smallest possible OAuth scope per integration:
- Google Ads:
adwords(read). Write scope is opt-in per agent at the moment the user enables auto-apply, with explicit re-consent. Full justification: /docs/google-oauth-scope-justification. - Meta Ads:
ads_read,business_management(read-only). Full justification: /docs/meta-oauth-scope-justification. - Shopify:
read_orders,read_products. We never requestwrite_*scopes.
Data storage + tenant isolation
Production data lives in Neon Postgres (us-east-2). All tenant-scoped tables include a userId column; every API route filters by the authenticated user's id (via NextAuth session) before returning any row. No row-level policy bypass exists for staff or anyone else — staff access requires the same auth check plus a server-side role check against an explicit allowlist in lib/roles.ts.
We do not log full ad-account contents to application logs. Stack traces sent to our Sentinel (/status) are scrubbed of token-shaped strings before storage.
Third-party data sharing
Two third parties touch your data and only at request time:
- Anthropic (Claude API). Campaign metrics (numbers + campaign names, no PII about your customers) are sent when an AI agent runs. We use Anthropic's zero-data-retention mode — they don't retain the data for training or any other purpose.
- Stripe. Billing details only — your card, your billing address, your tax id. Stripe is the card-of-record processor. We never store the PAN, CVV, or full card number on our side; the only thing we hold is Stripe's
customerId+ theirpaymentFingerprint(used for abuse prevention).
We do not use your data for advertising, retargeting, or any kind of analytics resale. We do not train models on your data.
Data export + deletion
Under GDPR Art. 15 (data access) and Art. 17 (erasure), you can:
- Export everything we have about your account as a single JSON file from Data & privacy settings.
- Hard-delete your account. We soft-delete on click (reversible for 14 days to protect against accidental clicks), then a nightly cron permanently removes all records.
For users on a Meta-integrated account who use Meta's in-platform deletion request, /data-deletion handles the Meta-initiated callback automatically.
Incident response
Production build errors are captured by our Vercel Sentinel webhook into deduplicated BugTicket rows, with a daily digest email to admin@fixadspend.ai. Severity 1 incidents (data loss, auth bypass) trigger an immediate page to the on-call engineer's phone via Twilio.
If a user's data is potentially affected, we commit to email-notifying every affected user within 72 hours and publishing a public post-mortem on the blog within 14 days.
Procurement / vendor questionnaire? Email admin@fixadspend.ai — we'll turn it around within two business days.
