ReTestimonial
Sign inStart free
SECURITY

Security you can read,
not just trust.

Most security pages are a wall of badges. This one is a list of engineering decisions — signed deliveries, scrubbed logs, scoped queries — each traceable to code. And where we haven't earned a claim yet, we say so out loud.

OUTBOUND WEBHOOK DELIVERYEXAMPLE
POST https://hooks.yourapp.com/retestimonial
Idempotency-Key: whd_01J9ZKD4
X-Signature: t=1784304000,v1=6b3fa41208c9e5d7
HMAC-SHA256 SIGNATURE VERIFIEDsigned over "timestamp.payload"
every control on this page applies to every plan — including free
/architectureWhere your data lives

A short list of well-run places. Boring on purpose.

YOUR VISITORBrowserHTTPS onlyHSTS remembered
THE EDGEVercel + Workerssecurity headerscached public pages
THE APPApplicationauth middlewareproject-scoped queries
THE STORESPostgres · R2 · Muxprovider-encrypted at rest
Clerk
AUTHENTICATION

Sign-in, sessions, and OAuth — credentials never stored by us.

Vercel
HOSTING

The application and its APIs, deployed atomically.

Supabase
POSTGRES DATABASE

Testimonials, projects, and settings live here.

Cloudflare
STORAGE + EDGE

R2 object storage for media; Workers serve embeds and walls.

Mux
VIDEO

Video testimonial hosting, streaming, and transcripts.

Resend
EMAIL

Outbound notifications and testimonial invites.

Stripe
PAYMENTS

Subscriptions and billing — card data stays on their pages.

Upstash
RATE LIMITING

Redis-backed request limits on public endpoints.

On encryption, precisely: traffic is TLS in transit everywhere, with a Strict-Transport-Security policy so browsers refuse to downgrade. At rest, our database and storage providers encrypt customer data as part of their platforms — that's their guarantee, and we name it as theirs rather than dressing it up as our own engineering. The privacy policy carries the authoritative sub-processor commitments.

Strict-Transport-SecurityX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originX-Frame-Options: SAMEORIGIN**widgets and collection forms are intentionally embeddable — that's the product.
/webhooksSigned on the way out

Deliveries you can verify. Destinations we refuse.

Webhooks are where a SaaS reaches out and touches your infrastructure — so this is where we spent the paranoia. Every event your endpoints receive is signed; every URL you configure is treated as hostile until proven public.

AUTHENTICITY

Four lines to verify any delivery

Each endpoint gets its own whsec_ signing secret — 32 random bytes. The header your server receives is X-Signature: t=…,v1=…, and verifying it takes four steps:

01Take the raw request body — before any parsing.
02Compute HMAC-SHA256(secret, timestamp + "." + body).
03Compare digests in constant time (timingSafeEqual). Match → authentic and untampered.
04Reject stale timestamps — the replay window is yours to enforce.
SECRET ROTATION
whsec_ v1whsec_ v2

Signing secrets are versioned. Rotate one and any in-flight delivery still carrying the old version is canceled before send — nothing ever goes out signed with a secret you've revoked.

SSRF DEFENSE

A URL is a claim. We check it twice.

A webhook URL could point anywhere — including at infrastructure that was never meant to be reachable. So before any delivery leaves, the destination has to survive this list:

DESTINATIONS WE REFUSE
http:// (plaintext)HTTPS onlyREFUSED
169.254.169.254cloud metadataREFUSED
10.0.0.0/8 · 192.168.0.0/16private rangesREFUSED
localhost · 127.0.0.1 · ::1loopbackREFUSED
https://user:pass@…embedded credentialsREFUSED
302 → anywhereredirects never followedREFUSED
DNS RESOLVED
EVERY IP VALIDATED
SOCKET PINNED

The connection is pinned to the exact IP address that passed validation, so a DNS answer can't change between the check and the connect — the classic rebinding trick, closed. Checks cover IPv4 and IPv6, including IPv4-mapped forms.

Rolling-hour delivery caps per endpoint and per project keep a misconfigured integration from becoming a firehose. Stored delivery logs strip cookies and authorization headers, and bodies are truncated.

/dataWhat we keep, and for how long

Deletion that actually deletes. Retention that actually expires.

Erasure, transactionally

When a testimonial is deleted — say, a GDPR erasure request — the person's name, email, and content are scrubbed from webhook delivery logs in the same database transaction as the delete itself. Not on the next cleanup run. The only thing left behind is a no-PII audit record saying a deletion happened.

Video assets enter a durable deletion queue and are removed from the video host once nothing references them — a crash between steps retries rather than leaking.

One click out, permanently

Testimonial invite emails carry RFC 8058 one-click unsubscribe headers — the kind mail clients render as a native button. An unsubscribe lands on a per-project suppression list that is checked before every send, so a suppressed address is never emailed again by that project.

RETENTION SCHEDULE · ENFORCED BY CRON
SURFACEWINDOW
In-app notifications30 days
Invite webhook logs30 days
Webhook deliveries — delivered30 days
Webhook deliveries — failed90 days
Recording-session IP + user-agentredacted at 90 days

Old operational records don't wait for someone to remember them — scheduled jobs purge on these windows. For rights requests (access, erasure, portability), the privacy policy is the operative document.

/accessWho can touch what

Scoped by default. Denied by default.

Sign-in, handled by Clerk

Google OAuth or email/password with email verification codes — credentials live with a dedicated auth provider, not in our database. The dashboard, billing, and settings sit behind authentication middleware; public pages are public by design, and nothing else is.

Roles that mean something

Team access is role-based with granular permission keys, and every role can be overridden per project — down to NO_ACCESS. Invitations expire after 7 days if unaccepted.

OWNERADMINMEMBERVIEWERNO_ACCESS
Every query carries the tenant
WHERE id = $recordId AND project_id = $projectId

Application queries are scoped by project in addition to the record's own id — a leaked or guessed id from another workspace resolves to nothing. This is application-level scoping enforced in our data layer, and we call it that plainly — not database row-level security.

Secrets between our own services

The edge workers that serve embeds and walls call internal APIs with bearer keys, and every scheduled job authenticates with a cron secret. Unauthorized attempts are rejected, logged, and alerted — and the auth checks themselves are covered by an automated test suite, because a gate you don't test is a gate you don't have.

/appsecLayers before the database

Nothing user-shaped is trusted. Nothing unsanitized gets through.

EDGE & HEADERSHSTS, nosniff, referrer policy, clickjacking protection on app pages.
RATE LIMITSRedis-backed limits on public and abuse-prone endpoints; hourly outbox caps.
VALIDATION & SANITIZATIONEverything user-shaped is allowlisted, typed, and clamped before use.
MONITORING & ALERTSError tracking with PII off by default; throttled security alerts.
Rich text, allowlisted

User-submitted content passes a strict allowlist sanitizer: scripts, event handlers, and unsafe URL schemes are stripped; links are forced to safe protocols and rel attributes.

Widget styling, validated per-field

Customer-supplied widget styling is validated value-by-value — colors, pixel sizes, font weights, font names — before it reaches rendered CSS. Malformed values degrade to defaults instead of injecting.

Public forms, spam-scored

Collection forms run honeypot fields, per-IP hourly limits, duplicate-content hashing, blocklists, and weighted heuristics — abuse is scored and quarantined before it reaches your inbox.

Links, gated to http(s)

URLs your customers type — custom-field links, testimonial links — only ever render as clickable when they parse as plain http or https. Anything else displays as inert text.

Tokens encrypted where we hold them

Where we do hold third-party credentials — G2 integration tokens — they're encrypted with AES-256-GCM before storage, with per-value random IVs.

Monitoring that minimizes PII

Error monitoring runs with default PII collection disabled. Unauthorized internal-API and cron attempts raise alerts — throttled, so a spoofed flood can't drown the signal.

/paymentsMoney, at arm's length

Your card never meets our servers. By design.

Checkout and billing management happen on Stripe's hosted, PCI-compliant pages — card numbers are entered there and never pass through ReTestimonial. Incoming billing events are trusted only after their Stripe signature verifies against the raw request body.

Gift-card reward webhooks (Tremendous) are verified with HMAC-SHA256 using constant-time comparison, and every event carries idempotency — a replayed notification can't double-fulfill a reward.

/honestyThe fine print, in large type

What we don't claim. Yet.

Security pages fail in one direction: overclaiming. So here is the list a diligent buyer would dig for anyway, stated up front.

NOT YETSOC 2 / ISO 27001

No certificate, no auditor's letterhead. We won't rent a badge to imply one.

INSTEAD

Documented internal security reviews for substantial new features, automated security test suites in CI, and this page — where each claim traces to code.

NOT YETThird-party penetration test

None commissioned yet.

INSTEAD

Engineering controls you can reason about — signed deliveries, pinned sockets, scoped queries — with adversarial internal review and tests around the sharpest edges.

NOT YETEnterprise SSO / enforced MFA

No SAML, no enforced authenticator-app MFA today.

INSTEAD

Clerk-managed sign-in with Google OAuth, email/password, and email verification codes. If SSO is what's blocking your team — tell us. Demand moves roadmaps.

NOT YETDatabase row-level security

Tenant isolation is not enforced by Postgres RLS.

INSTEAD

Application-level scoping: every query carries the project id alongside the record id, in the data layer we own and test. We name it what it is.

A checklist you can verify beats a badge you have to take on faith.

/disclosureFound something?

Tell us. We'll listen like it matters — because it does.

If you believe you've found a vulnerability, email privacy@retestimonial.com — the same contact published in /.well-known/security.txt. We read every report and acknowledge as fast as we honestly can.

Please don't access, modify, or retain data that isn't yours while demonstrating an issue.
Good-faith research won't be met with legal threats.
No public bounty program yet — but we credit reporters who want it.
/faqAsked by procurement

Straight answers, no asterisks.

Certifications, signatures, sub-processors, deletion — the questions security reviews actually ask, answered the way we'd want them answered.

01Are you SOC 2 or ISO 27001 certified?

Not yet — and we won't imply otherwise with a badge wall. What exists instead: documented internal security reviews for substantial new features, automated security test suites in CI, and the specific engineering controls on this page, each of which maps to real code. If your procurement process needs something specific answered, email privacy@retestimonial.com and we'll answer plainly.

02How do I verify webhook signatures?

Every outbound webhook carries an X-Signature header of the form t=<unix-timestamp>,v1=<hex-digest>, where the digest is HMAC-SHA256 over "timestamp.payload" using your endpoint's signing secret. Recompute the HMAC over the raw request body, compare digests with a constant-time function, and reject anything whose timestamp falls outside your tolerance window — the timestamp is part of the signed content precisely so your receiver can refuse replays.

03Where is my data stored, and who are your sub-processors?

The application runs on Vercel; data lives in a Supabase-hosted Postgres database; media lives in Cloudflare R2, and video with Mux; email goes out through Resend; payments run through Stripe; authentication is handled by Clerk. These providers encrypt data at rest as part of their platforms. The privacy policy carries the authoritative, always-current sub-processor list.

04What actually happens when I delete a testimonial?

The record is deleted, and in the same database transaction the person's data is scrubbed from webhook delivery logs rather than waiting for retention to expire — the only thing left behind is a no-PII audit record saying a deletion happened. Video assets enter a durable deletion queue and are removed from the video host once nothing references them.

05Do you support SSO or enforced MFA?

Not today. Sign-in is handled by Clerk with Google OAuth and email/password plus email verification codes. There's no SAML or enterprise SSO and no enforced authenticator-app MFA yet — if that's what's blocking your team, tell us. That's exactly the kind of demand signal that moves a roadmap.

06Does my card data ever touch your servers?

No. Checkout and billing management happen on Stripe's hosted, PCI-compliant pages — card numbers are entered there and never pass through ReTestimonial. On our side, we verify Stripe's webhook signatures on the raw request body before trusting any billing event.

07How do I report a security vulnerability?

Email privacy@retestimonial.com — the same contact listed in our security.txt at /.well-known/security.txt. We read every report and acknowledge as fast as we honestly can. Please don't access data that isn't yours while demonstrating an issue. There's no public bounty program yet, but we credit reporters who want it.

GET /security → 200 OK

Proof, for the people asking for proof.

You collect testimonials because claims need evidence. We built the platform the same way.