Skip to content

Trust Center

Answers your reviewer can check

Your contacts, your calls and your evidence sit behind a fence Postgres enforces, and the API connects as a role that cannot lift it. Every attempt leaves a compliance record you can export. Your carrier account, your numbers and your caller-ID reputation stay in your name, so the day you leave is an export rather than a negotiation.

Nothing below is a statement about how carefully we work. Each one is a mechanism with a failure mode, which is what makes it worth a reviewer’s time and worth defending to whoever sent them.

The review

Four questions decide this

A security review turns on a small number of questions, and the answers that survive it are the ones somebody can go and check afterwards. Here they are in the order they arrive, each with the short answer and where the long one lives.

  1. 1

    Can another customer’s bug reach our rows?

    No application code has to remember to filter. Postgres decides what a query may see, and it decides before our code gets a vote.

    Read the long answer
  2. 2

    Where does the carrier credential we hand over live?

    Sealed, in a store the database layer holds no key for. The layer that can open it never opens a database connection, and the build fails if either half imports the other.

    Read the long answer
  3. 3

    Who at your company can open our account?

    A console reading through a role that owns nothing and can write one thing: a new audit row. A session that can change anything needs a second person, and that rule is a constraint on a table.

    Read the long answer
  4. 4

    What does the day we leave look like?

    Quiet. The carrier account, the numbers and the caller-ID reputation were always in your name, and your records come out as rows through an API you already use.

    Read the long answer

Tenant isolation

The fence is in the database, not in our code

Most vendors answer this question with an assurance about their own application, and that answer is worth exactly as much as their next code review. Ours is smaller and harder to undo. Every table holding your data carries a row-level security policy, and the API connects to Postgres as a role those policies bind. A missing filter in our code is not a breach in your account, because the entitlement was settled a layer below the query.

Every tenant table carries a policy

One policy per table and the same predicate every time: this row belongs to the tenant bound to this transaction. The binding is the first statement the transaction runs and it is scoped to that transaction, so a pooled connection handed straight to the next caller carries nothing forward from the last one.

We connect as a role the policies bind

Postgres exempts a table’s owner from its own policies, which is how isolation usually turns into a comment. So there are two connection strings and they are different roles. Migrations run as the owner. The API never does: its role owns no table, holds no bypass, and carries data privileges and nothing else.

A query with no tenant bound raises

The function the policies call raises when the tenant is unset instead of quietly matching nothing. So the failure mode of a mistake in our code is an error on our side, never another customer’s contacts on yours. The silent empty result is the more dangerous outcome, and it is the one that cannot happen here.

A misconfigured deployment serves nothing

At startup the API interrogates the role it just connected as. Is row-level security on. Does this role own the table. Does it hold a bypass. Does it reach anything it should not. Any of those and it refuses to serve. The same check runs on every readiness probe, so a task that drifts is pulled out of rotation rather than left answering requests.

Coverage is a test, not a checklist

A test reads the Postgres catalog on every build and fails if a table carrying a tenant id has no policy on it. New tables cannot quietly arrive outside the fence, which is the way isolation actually erodes: not in one bad decision, but in the fortieth table nobody thought about.

History cannot be rewritten by the application

The audit table has no update policy and no delete policy for any role in the database, so a correction is a new row pointing at the row it corrects. The application tier could not rewrite what happened even by bug, which is the property that makes an old record worth reading.

That is a different kind of answer from a paragraph about our engineering culture, and it is the one we would rather be judged on. It is also the answer that keeps holding while we ship: the policies, the role, the boot check and the catalog test all still apply to code nobody has written yet.

Credentials

The most sensitive thing you hand us, and where it goes

You connect a carrier account. That credential can spend your money and speak in your company’s name, so it is worth knowing precisely what happens to it, and worth knowing what would have to go wrong before anyone could read one.

Sealed before it reaches a table

Your carrier credential is sealed with AES-256-GCM under a nonce drawn fresh for that record, and the key it was sealed under is named inside the sealed bytes. Rotation is adding a key and pointing new writes at it: yesterday’s records still open, and nothing has to be re-encrypted against a deadline.

The two halves cannot reach each other

The code that talks to the database handles bytes and holds no key. The code that holds the key never opens a connection. That is not a convention somebody maintains: an import rule fails the build if either half so much as imports the other, and the rule runs before a change can merge.

Write-only, in and out

A carrier credential is never returned by an endpoint, an export, a log line, or an error body. It is typed so that a request body reaching a stack trace prints as asterisks. The only readback anywhere is whether one is stored. A test compiles the actual insert statement and asserts no credential ever gets bound into it.

Recordings under a key of their own

Call audio sits in a bucket with public access blocked, transport required, and its own rotating key. A write that does not name that exact key is denied by the bucket rather than accepted quietly, so a future uploader misconfigured by us fails instead of putting one call’s audio somewhere weaker.

The database has no door to the internet

It runs in isolated subnets with no route out and no public endpoint, reachable only from the API’s own security group. There is nothing to scan, because there is nothing addressable.

Your sandbox cannot dial a stranger

Test and live are a property of the credential, not a field a client asserts. A test key routes every call to the mock provider, so no dial to the public network can originate from one and no billable usage comes out of one. Nobody ships a mistake into somebody’s evening.

Access is shaped the same way. Every request is authenticated, keys carry scopes and rotate in place, and key management is treated as a privilege boundary rather than as record editing: a key cannot mint a wider key, cannot mint a live one from a test one, and cannot revoke a credential broader than itself. Passwords are stored with argon2, the browser holds an opaque session cookie and no credential, and every minting, rotation and revocation writes its audit row in the same transaction as the change.

Our own people

Who can open your account, and on whose signature

This is the question a reviewer expects a vague answer to, because most vendors give one. The honest version is that support has to be able to see something, and the whole question is what that something is, who authorized it, and what it left behind.

So the staff plane is built as a separate set of privileges rather than as an elevated version of yours. It reads through its own database role. It cannot write your rows. The one thing it can add to the schema is a record of itself.

A different role, with one write in the whole schema

The staff console reads through a database role that owns no table and holds no write on your data. Its single write grant anywhere in the schema is an insert into the audit log. It is not a member of the runtime role, and the API refuses to start if those two ever become the same principal.

Staff never hold your API key

Support access mints a credential of its own class instead. The secret comes back exactly once, in a URL fragment, so it never lands in a server log, a CDN access log, or a referrer header, and only its SHA-256 is stored. Nothing in that path can be replayed from a log line later.

A session that can change anything needs two people

It requires a live approval from a different staff member, and different is a check constraint on the approvals table rather than an if statement somebody could edit. The read-only session is the default, and the one that can write has to be asked for and granted.

The reason is typed before the action, not after

Every console action carries the operator’s own words for why, captured with the action rather than prompted afterwards, because a reason invented later is a caption. The change and the row that records it commit together or neither lands, so the trail cannot disagree with the database about what happened.

Every call

Controls with no off switch, and a record for each attempt

Compliance here is product behavior rather than paperwork. Every line below holds on every plan, for every tenant, through every entry point, and each one is something your own team can watch happen on a sandbox account before you sign anything.

Four controls, and nothing switches them off

The AI-disclosure opener. Opt-out detection that writes your internal do-not-call list mid-call. Suppression against that list at the batch run and again at dial time. Phone normalization. Not because a policy forbids turning them off: a compliance mode can only name checks drawn from a waivable set, and these four are not members of it. Waiving the opener is not a value anyone can construct, express in JSON, or store, and two tests fail the build if that ever stops being true.

The opener is an interlock, not a convention

The model adapters live behind a gate the call driver holds no reference to. Reaching the model before the opener has played raises instead of dialing, and a call whose opener cannot play ends. So the sentence a regulator attributes to whoever originated the artificial voice is the one sentence in the system that cannot be prompt-injected away.

Every attempt answers for itself

One compliance record per attempt, written as it happens: which checks ran, which your signature covered and who signed it, the window and caps evaluated in the contact’s local time, the consent record relied on, and why the call went out or was stopped. It sits on the attempt row and comes out through the exports API with the rows intact.

The refusals are records too

A dial stopped at the gate is written with the same care as one that connected, because the call you did not place is the one you will be asked about. The gate re-runs as the channel is taken and stamps its result on the passing path as well, so a dial re-verified at launch is distinguishable from one that only ever cleared queueing.

Uncertainty stops the dial

Only an affirmative pass authorizes a call. A check that cannot complete blocks it, and a stage that did not run is never written as a stage that found nothing. Your record can tell checked, clear, and nobody looked apart, which is the difference between evidence and a log.

Consent is a record, not a checkbox

Every dial stands on a consent record: its class, the purposes it covers, where it came from, and a SHA-256 of the proof behind it, read again at dial time instead of trusted from an import. A campaign whose purpose falls outside what that person agreed to is suppressed before it dials, and the record the call stood on is named in the file.

Stop means stop, from the same second

A mid-call opt-out is a deterministic path the model does not get to argue with: the phrase list and the keypress are code constants, and the suppression commits before the call ends rather than at teardown. The next dial reads it live. It lands in a ledger with no channel column and no expiry, so it cannot fall out of step with itself.

What you signed, hash for hash

Acceptances are append-only and each one records the document version, the SHA-256 of the exact wording presented, the signer, and a server-stamped address and time. This site and the product serve one source document, so what a signer accepted and what we publish cannot drift apart afterwards.

A question about a call from March becomes a lookup rather than an investigation, and the answer comes out of the record rather than out of somebody’s memory. The mechanism is written up in full on the compliance engine, and the record itself is rendered key for key, including a refused attempt, on the evidence page.

Ownership and exit

Nothing here is expensive to leave

Lock-in is a procurement question before it is a technical one, and in outbound calling the expensive things to move are the carrier account, the numbers, the registrations and the reputation attached to them. All four are already in your name, so a decision to stop is a decision, not a project.

Your carrier, your rates, your caller ID

Every call a stranger answers goes out on the Twilio account you own: your numbers, your registrations, your negotiated rates, and the caller-ID reputation attached to them. Twilio bills you those minutes at the rate you already hold, so a Vocapable invoice carries no carrier line and no number rental at all.

And no setting anywhere changes that

A production dial routed onto a Vocapable-owned number is refused at the gate. It is not a mode a plan can select or a flag support can flip: the refusal sits outside the waivable vocabulary entirely, and a test walks every purpose, every consent basis and every mode to prove no combination reaches it. Your MSA §6.2 puts the same limit in the contract.

Your records come out as rows

CSV export jobs cover supported record types: contacts, contact attempts, calls, survey responses, shift confirmations, appointments, internal do-not-call entries, scrub results, usage events, and caller requests. A completed job provides a signed download for its time-bounded window. Available recordings and transcripts are fetched through authenticated /v1 call reads.

The dashboard is optional, and always was

Product resources use /v1, with cursor pagination on every list and a configured signed webhook endpoint for enabled events. Signup, onboarding, and first-key setup use browser flows. If your stack is the system of record on day one, nothing about leaving is an exercise: you were already holding the data.

Cold outreach, on your own data, signed for

Lists you purchased, rented, or had appended are yours to call. They run under the Cold Outreach & Data Provenance Waiver you execute, which records that the data is yours under your own supplier agreement and that the consent basis, the jurisdictions and the responsibility for that program stay on your side. Scraped and unauthorized-extraction contacts are refused however they arrive, by upload or by API. Every import carries a signed attestation of where the contacts came from, the four controls above are unchanged by the waiver, and every attempt writes its record either way.

Cards never touch us

Payment is handled by Stripe. The browser never receives a secret from us, and no card detail is stored on our side, so the part of your review that is about payment data is a short conversation.

The same shape holds on the way in. Bring the account you already have, keep the rates you already negotiated, and run the whole dial path against simulated telephony first, every gate live and failing closed, before a single number rings anywhere. See how the telephony works and what the plans cost.

The division

Clear lines beat warm assurances

Here is the division, so nothing in your posture rests on an assumption about ours. A signature records each half, which is why an answer six months from now can name a document and a person rather than a recollection.

One signature, riding every record

Before the first production dial you sign an attestation naming the screening arrangement you already run. It does not sit in a folder after that: its reference and its signer ride into every record, so “who screened this number, and on which document” is answered on the call itself instead of in somebody’s email. Keep the supplier you have and the pricing you negotiated for it, and never pay twice for a lookup you already buy.

What happens after the call is yours to run

Each event you enable is signed and retried. A call.ended event arrives at call completion with how the call ended, its duration, related record identifiers, and evidence availability; call.analyzed adds post-call analysis later. Survey and shift outcomes arrive through their own signed events and resources. Your rep or coordinator can fetch available call evidence through authenticated /v1 reads and work the next step in the Inbox.

The law where you dial is your call to make

The platform enforces the policy state on every attempt, quiet hours and frequency caps and consent standards, and records the values it evaluated, so the proof of what was enforced is yours to produce on demand. You choose who gets called, and the duty that choice carries where you dial stays with you: what the law requires there is determined by you and your advisors, and we say so here rather than in a footnote.

Your data, and it leaves whenever you want it to

Call recordings, transcripts, and compliance records are processed on your behalf as a service provider. Retention, deletion, and what happens to the audio when an agreement ends are written into the Data Processing Addendum and the Privacy Policy, and both are published in full below before anybody signs.

Scope of service

Where the service begins and ends, stated plainly so your reviewer does not have to infer it from what a page happens to mention.

  • Vocapable serves US calling today, and the privacy machinery we operate is built on CCPA and CPRA.
  • Calls involving protected health information are outside our Acceptable Use Policy.
  • Campaigns run on the contacts you supply, captured or acquired, and every import carries a signed attestation of where they came from.
  • When caller voicemail is recorded, it is available on the call record for your team to play.

Running a security review

Send the questionnaire. You get a written answer, from the person who built it.

Mail it to support@capstralabs.com and you get direct answers about the controls themselves rather than a portal invitation. We publish the control descriptions instead of holding them back for a questionnaire, which is why most of your list is already answered above and the rest is a reply rather than a process.

The shortest version of the whole thing, written for the person who has to summarize it upward, is here: how your customer data is protected.

Questions reviewers ask first

What stops one customer’s data reaching another customer’s account?

Postgres, before our code runs. Every table holding tenant data carries a row-level security policy keyed to the tenant bound to the current transaction, and the API connects as a role those policies bind: it owns no table, holds no bypass, and has data privileges and nothing else. A query that reaches those tables with no tenant bound raises rather than returning rows, so the failure mode of a bug on our side is an error on our side. A test reads the database catalog on every build and fails if a table carrying a tenant id has no policy on it.

What happens if you deploy it wrong?

The API refuses to serve. At startup it interrogates the database role it just connected as and stops if row-level security is off, if the role owns the table, if it holds a bypass, or if it reaches anything it should not. The same check runs on every readiness probe, so a task that drifts after startup is pulled out of rotation instead of left answering requests. Failing at boot is the behavior you want from an isolation boundary, because the alternative is a deployment that looks healthy and is not.

Where do the carrier credentials we connect actually live?

Sealed with AES-256-GCM in a store whose database half holds bytes and no key, and whose key-holding half never opens a database connection. An import rule fails the build if either half imports the other, so the separation survives the next refactor rather than depending on somebody remembering it. The credential is never returned by an endpoint, an export, a log line, or an error body, and a support view of your account carries the address of the record rather than anything inside it.

Can someone on your team read our contacts?

The staff console reads through a database role that owns no table and whose only write grant in the whole schema is an insert into the audit log, and the API refuses to start if that role and the runtime role are ever the same principal. Staff hold no customer API key: support access mints a credential of its own class, returned once and stored only as a hash. A session that can change anything requires a live approval from a different staff member, enforced as a constraint on the approvals table, and every action carries the operator’s own stated reason, written in the same transaction as the change it describes.

Can any plan, flag, or support ticket switch the compliance controls off?

No, and not because a policy forbids it. A tenant’s compliance mode can only name checks drawn from a waivable set, and the four floor controls are not members of it; the unwaivable stages are derived by set difference rather than listed, so they cannot be omitted by an edit. Waiving the disclosure opener is not a state anyone can construct, express in JSON, or store, and two tests fail the build if that stops being true. They are also never metered: the local stages are excluded from billable lookups by construction, so there is no configuration in which the floor is charged for or switched off to save money.

What does leaving look like?

An export, on your own schedule. The Twilio account, the numbers, the registrations and the caller-ID reputation were always in your name, so nothing has to be migrated off ours. CSV jobs cover supported record types: contacts, contact attempts, calls, survey responses, shift confirmations, appointments, internal do-not-call entries, scrub results, usage events, and caller requests. Available recordings and transcripts are fetched through authenticated /v1 call reads. Product resources use /v1, while signup, onboarding, and first-key setup remain browser flows, so a team that integrated on day one is already holding its own records.

We have a security questionnaire. What do we get back?

Written answers about the controls themselves, from the people who built them, at support@capstralabs.com. The control descriptions are published rather than held back for a questionnaire, which is what this page is: most of what a reviewer asks is already answered above, and the rest is a reply rather than a process.

Read the whole set before you sign any of it

Every document that forms the agreement is published here in full, and the product serves the same source text, hash for hash. Nothing is incorporated by reference into a page you cannot open, and nothing arrives for the first time in a signature request.

Check it on your own account

Open a sandbox, run the whole dial path against simulated telephony, and read a compliance record from a call you ran yourself. The floor runs there exactly as it runs in production, and nothing is pointed at anyone.