//Trust
Compliance and data security
Facts checked against official sources · September 9, 2026
Political texting is regulated, voter files come with contract terms, and a campaign's data is the most sensitive thing it owns. Culper treats the boring parts as product: a registration wizard that will not let you text before the carriers approve you, an opt-out list no campaign can override, and database-level isolation between campaigns.
This page describes what is actually built. The formal statements live at /security and /privacy; this is the working explanation of how those commitments show up in the product.
What compliance and data security cover in Culper
Five things. A2P 10DLC: a brand and a political use case are registered before the SMS page will send. TCPA: consent is captured with the required disclosure, every STOP is honored instantly, and opt-outs go on a global list. Isolation: every campaign-scoped table has a row-level security policy keyed to membership and role. Terms: acceptance is recorded per user and version. Support: tickets are acknowledged, threaded, and screened for abuse.
The architecture is SOC-aware, but a formal SOC 2 audit has not been completed. Card numbers never touch the platform; online donation processing is in development, not live.
A2P 10DLC registration workflow
The SMS page opens with two paths. Register New Service walks a new campaign through the registration the FCC and carriers require for political texting; the wizard states that approval typically takes 8 to 14 days. Connect Existing Service is for a campaign that already has an approved Twilio messaging service: paste the service SID and send without a new wait.
Registration is three steps with a status panel. Step 1, brand registration, is your organization's identity with The Campaign Registry; it moves from not started to pending to approved or rejected. Step 2, campaign registration, is the political use case reviewed by Twilio and unlocks once the brand is pending or approved. Step 3, phone number assignment, follows campaign approval.
Because a messaging service can hold numbers and still be unable to send, the SMS page also asks Twilio whether an approved A2P campaign is attached to your service, and shows one with none as not ready rather than approved.
Consent capture
Consent is stored in a consent log with the campaign, the channel (SMS, email, or voice), the contact in normalized form, a consent type (express written, opt-in form, imported, or manual), and a source. The public join page is the main capture point: an unchecked-by-default box, disabled until a phone number is entered, labeled with the full disclosure. Only a checked box with a usable number writes a record.
Consent is per campaign and per channel: opting in to your texts is not opting in to anyone else's. When a person opts out on a channel, every un-revoked consent record for that contact on that channel is revoked across all campaigns at once.
The sending pipeline includes a consent gate for SMS that can hold back numbers lacking an active consent record for the sending campaign. It is operated at the platform level rather than toggled per campaign; this page describes the design, not a particular default.
STOP and the global suppression list
Every inbound text arrives at a webhook that first verifies Twilio's signature, so a forged request cannot pollute the opt-out list. If the body is STOP, UNSUBSCRIBE, CANCEL, END, or QUIT, the number is added to the suppression list, its consent records are revoked everywhere, and the opt-out is mirrored into any peer-to-peer conversation so the texter sees it and the contact leaves their queue.
The suppression list is campaign-agnostic: once a phone or email is on it for a channel, no campaign on the platform can reach that contact on that channel through Culper. Emails carry a signed one-click unsubscribe link that does the same thing and never expires; the list, not the token, is the source of truth.
Every send path checks the list in canonical form (E.164 for phones, lower-case for email), so a number opted out as (555) 123-4567 still blocks a send to +15551234567. The check fails closed: if the lookup errors, the send aborts. Each recipient is claimed in the database before the provider call, so a retried batch never texts the same person twice.
Per-campaign row-level security and role gates
Every campaign-scoped table carries a campaign column and a row-level security policy that asks one question: does this user hold at least this role on this campaign? The check ignores soft-removed memberships, so a removed member has no more access than a stranger. RLS is the last line of defense; the application checks first.
Application gates are role-specific. Unauthorized callers get a 404, not a 403, so a resource's existence is not confirmed. The voter pages require Owner or Admin, or Staff explicitly granted voter access. Bulk CSV export is limited to Owners and Admins, with one scoped exception for a precinct chair exporting their own precinct after an admin grants it. Some heavy reads use a direct database pool that bypasses RLS, which is why those page gates are treated as load-bearing.
Archived campaigns reject every write through database triggers, including writes that bypass RLS. The natural-language query layer never sends voter rows to the model; it emits a constrained grammar compiled to parameterized SQL scoped to the campaign, and every prompt and row count is logged. Support access for Culper staff is an environment allowlist, not a database role, and a key-health endpoint limited to that allowlist reports whether each secret works without returning a value.
Terms acceptance
The current terms are version v1.1, dated 2026-08-21, which added the SMS program terms carriers require and the restrictions on reselling or retaining data provided to a campaign. Acceptance is recorded per user and per version with a timestamp, IP address, and platform (web or iOS), written on the server at signup so it works before a session exists.
When the version changes, a re-acceptance screen appears at the next login, deliberately at the login boundary so a volunteer mid-walk is never interrupted. Since August 2026 the database policy lets a user insert only their own acceptance row; the record is the evidence of agreement, so it must not be forgeable.
How uploaded voter files are handled
A file you upload goes to a private storage bucket, scoped to your campaign, readable only by staff and above on that campaign. During column mapping, only headers and a few redacted sample values reach the mapping model: phones, emails, birth dates, names, and free text are replaced with shape-preserving stand-ins, and full rows are never sent. The rows land in a campaign-scoped silo no other campaign can read, which is also how vendor per-client terms are respected.
The campaign owns what it uploads and the platform processes it on the campaign's behalf. Deleting an import requires typing the original filename, soft-deletes its voters in batches, is limited to one destructive action per campaign per hour, and can be undone for seven days. Data-subject requests about uploaded data go to the campaign; requests about platform-held records go to privacy@theculpernetwork.com.
Support tickets and abuse handling
Get Help in the dashboard files a ticket as Customization, Feature Request, Data Analysis, Report a Bug, or Other, and a zero-result voter query offers a data-analysis ticket with the question attached. Every ticket gets an eight-character reference and an acknowledgement email; replying to it threads back onto the same ticket. Data-analysis tickets are answered within 24 to 48 hours.
Before a ticket is saved, a deterministic screener flags requests to delete data at scale, SQL or system commands, prompt injection aimed at the agent that works the queue, mass export requests, privilege escalation, cross-campaign data requests, credential mentions, and requests to bypass opt-outs. It labels and never blocks: the ticket is always created and a person judges the matched text. Security issues go to security@theculpernetwork.com; confirmed incidents affecting customer data are disclosed without undue delay, typically within 72 hours.
How a local campaign uses it
Standing up a texting program, city council. In week one you start brand registration and put the join page on your push cards. Over the two-week review you collect 180 contacts, 110 with the text box checked. When Step 3 clears, your first send goes only to consented, non-suppressed numbers.
A STOP mid-wave, school board. During a 600-message get-out-the-vote send, 4 people reply STOP. Each is on the global list within seconds, and the next wave, and every other campaign's wave, skips them.
A field director leaves, county commissioner. You remove her from the Team page and revoke her walks. Her access ends immediately on web and mobile, she never had CSV export as Staff, and her completed walks stay in your data with a record of who removed her and when.
Why it matters
One class action over unconsented texts ends a local campaign. The political texting rules explain what TCPA and 10DLC require; the product is built so the easy path is the compliant one. Voter files come with contract terms too, and reading a voter file covers why a per-campaign silo is the only defensible way to hold one.
The security posture and privacy policy are the public commitments; the sections above are how they are kept.
What it does not do
Honest limits, so nothing here is mistaken for legal advice.
- No completed SOC 2 audit. The architecture is SOC-aware; the formal audit is forthcoming.
- No scrub against the federal Do Not Call registry. The suppression list is opt-outs collected through the platform.
- No consent import from a column in an uploaded file. A vendor's opt-in flag is not treated as consent.
- The send-time consent gate is operated by the platform, not a per-campaign setting.
- Not a substitute for counsel. Political SMS rules vary and change; confirm your program with a lawyer.
Works with
Compliance is not a separate module; it is enforced inside each channel.
- SMS outreach: the A2P wizard, the suppression check, and the consent gate live on the send path.
- Peer-to-peer texting: STOP replies are mirrored into the texter's queue.
- Voter database: the campaign-scoped silo, the import flow, and the off-roll gate on every send.
- Volunteer coordination: the roles and soft removal the security model depends on.
- Campaign page: the join page that captures consent.
Frequently asked questions
Can I text voters as soon as I sign up?
No. Political SMS requires a brand registered with The Campaign Registry and a campaign use case approved by Twilio, which the SMS page walks you through and which typically takes 8 to 14 days. If you already have an approved Twilio messaging service, you can connect it by SID, and the page checks with Twilio that an approved campaign is attached before it reports ready.
What happens when someone replies STOP?
The reply reaches a signature-verified webhook. The number is added to a global suppression list, every active SMS consent record for it is revoked across all campaigns, and any peer-to-peer conversation it is in is marked opted out. From that moment no campaign on the platform can text that number through Culper. UNSUBSCRIBE, CANCEL, END, and QUIT are honored the same way.
Can another campaign see my voter data?
No. Every campaign-scoped table has a row-level security policy that checks the caller's membership and role on that specific campaign, and uploaded files sit in a campaign-scoped storage path and silo. A person who belongs to two campaigns has two memberships and sees each campaign's data only while working inside it.
Can a staff member export my voter file?
No. Bulk CSV export is limited to the Owner and Admin roles. Staff can be granted in-app voter access but never export. The one exception is a precinct chair on a party or PAC campaign who has been explicitly granted export of their own precinct by an admin, and that grant is cleared on any role change or removal.
How is my acceptance of the terms recorded?
Per user and per terms version, with a timestamp, IP address, and platform, written on the server at signup. When the terms version changes, you are asked to accept again at your next login, and the database only allows a user to write their own acceptance row. The current version is v1.1, dated 2026-08-21.
What does the support abuse screen do?
It is a deterministic scan run on every ticket before it is saved. It flags large-scale deletion requests, SQL or system commands, prompt injection aimed at the agent working the queue, mass export requests, privilege escalation, cross-campaign data requests, credential mentions, and requests to bypass opt-outs. It labels rather than blocks: the ticket is always created and a person reviews the flagged text.
Run the boring parts right
Keep reading