Airlock

EngineeringPaymentsVoice AI

Airlock: PCI-safe card and eCheck payments for ElevenLabs Agents

Keypad digits reach the language model on ElevenLabs Agents today; Airlock puts a PCI DSS Level 1 vault in that path, so neither the model nor the merchant's servers ever see a card.

October 6, 2026Daniel Wilson KempLifted Holdings12 min read

Listen to the summary
Narrated by Scarlett · ElevenLabs Eleven v4
0:00

01The gap

A keypad that types into the model

In late August 2026, ElevenLabs Agents added caller keypad input. A caller can now press digits during a phone call, which is exactly what a payment needs. The catch is where those digits go: keypad digits are delivered to the agent's language model as an ordinary user message. A card number typed on the keypad becomes text in the model's context window.

ElevenLabs offers a setting for this, redact_input. Its documentation is precise about what it covers: it replaces keypad entries in stored transcripts, logs and analysis, and it says plainly that it

“does not hide digits from the agent during the live call.”

ElevenLabs documentation, redact_input

The obvious workaround is to isolate payment in a dedicated sub-agent and hand the call to it. We tested that against real agents.

Live finding · 2026-10-06

ElevenLabs re-sends the entire conversation, including typed digits, to the Custom LLM on every later turn — even after transfer_to_agent hands the call to another agent. Isolating payment in a sub-agent protects nothing.

Digits typed into the web widget's text box are also stored in the ElevenLabs transcript in plain text; redact_input covers keypad entries only.

There is no native secure-capture feature in the ElevenLabs changelog through 2026-09-28. The Stripe integration sends the caller a payment link, which means leaving the conversation to pay. If you want a caller to pay inside the call, the card has to pass through something — and today that something is the model.

Airlock changes what the model receives. Every model request from ElevenLabs goes first to a certified vault, which swaps keypad entries for short-lived tokens before anything else sees them. Below is the whole system, running live.

02Live demo

Pay a sandbox order. Watch where the card goes.

The call panel is a real ElevenLabs agent, Scarlett at Lifted Coffee Roasters, wired through Airlock exactly as described on this page; it speaks with Eleven v4 Turbo, the low-latency v4 model for live calls. The live data flow is the system as it runs: each packet is labelled with what is actually inside it at that hop, driven by Airlock's own event stream for your call.

There are two ways to deploy it. Pick one, start a call, ask to pay an order, then key in a test card. No call handy? Press Replay a real run under the diagram.

Sandbox only. The vault edge discards any card number that is not a published sandbox test card, and the demo agent is rate-limited. This demo charges NMI and Authorize.net sandboxes; no real money moves.
Scarlett · Lifted Coffee RoastersIdle

One click runs a real text-only call — ElevenLabs, the vault, Airlock and the NMI sandbox — with no one at the keyboard. No microphone.

Or drive it yourself:

Mic call or text-only chat. Both go through the same vault.
The conversation appears here. Try: “I'd like to pay for order A1042.”
Keypad — press # to send

A browser has no real DTMF, so this keypad collects your digits and sends them as one message when you press # — the same text a phone keypad produces in the model request.

Click or press Enter on the card to flip it

Live data flow Idle replay a real run, or start a call

Card digits exist only at the three PCI DSS Level 1 hops marked in amber.

The dashed box is the agent's own brain, relocated. With Custom LLM, ElevenAgents hands the thinking to your endpoint; Airlock passes normal turns to the merchant's model and never sends it card data. Voice, turn-taking and keypad input stay in ElevenAgents.

readyEvery packet is labelled with what is actually inside it at that hop.
    0 / 0
    Payload inspector

    Select a hop, or watch a packet move. You'll see the exact (redacted) payload at that point.

    Airlock event stream 0 events
    Airlock events appear here — kind, step, brand, last four, timings. Never a card number.

    Tip: NMI's sandbox declines amounts under $1.00, so order A0050 is the decline test. The gateway also refuses an identical card and amount within about 20 minutes (“that payment already went through”) — use a different order or card to retry.

    03How it works

    Two proxies, one script, no card in the middle

    An ElevenLabs agent has a language model as its brain. With Custom LLM, ElevenAgents hands that thinking to an endpoint you choose, while voice, turn-taking and keypad input stay in ElevenAgents. The model behind it is the merchant's own — any OpenAI-compatible model — not an extra AI added by Airlock. Airlock sets the Custom LLM URL to a vault's inbound proxy. The vault inspects the request before it is forwarded, turns keypad entries into tokens, and passes the rest to Airlock, an open-source service that holds only tokens. Airlock either passes the turn to the agent's model — never with card data — or, during a payment, runs a deterministic capture script itself. To charge, Airlock calls the vault's outbound proxy with token references; the vault fills in the real values and posts to the gateway.

    Here is one card payment, step by step.

    1. The caller asks to pay. ElevenLabs posts the conversation to the Custom LLM URL — the vault inbound proxy — with the proxy key in a request header.
    2. Speech passes through. Nothing card-like is in it, so the vault forwards it to Airlock, which passes it to the agent's model (the merchant's own). When the caller wants to pay, Airlock opens a capture.
    3. The caller keys the card number and presses #. ElevenLabs delivers the digits as a user message. At the vault, the inbound transform replaces them with a token and a marker such as [[airlock:kp len=16 last4=1111 brand=visa]]. That marker is all Airlock ever receives.
    4. Airlock runs the script, not the model. While a capture is open, Airlock speaks fixed prompts — expiry, security code, ZIP, save the card?, confirm — and collects one token per field.
    5. The caller confirms. Airlock sends the outbound proxy a charge whose card fields are token expressions like {{ token }}. Airlock's own key cannot resolve them: detokenization lives on a separate vault application attached only to the fixed-destination outbound proxies. The proxy builds the gateway body itself and posts to NMI or Authorize.net.
    6. A reduced reply comes back. Status, response code, auth code, AVS and CVV results — no card data. Airlock deletes the tokens; they would expire after 10 minutes anyway.
    7. The agent tells the caller. The model sees only the outcome, the brand and the last four digits.

    Two ways to deploy it

    Payment desk (recommended). The main agent, Scarlett, runs on ElevenLabs' own hosted language model — no Custom LLM, no merchant model. When the caller is ready to pay, she uses ElevenLabs' native transfer_to_agent to hand the call to a payment-desk agent. The desk agent's Custom LLM goes through the vault to Airlock with the header X-Airlock-Mode: desk. On the desk no language model is called at all: Airlock scripts every word — “You're on the secure payment line. Say ready when you have your card or bank details.”, then the total and the keypad prompts (for an eCheck, the name on the account first) — charges through the vault, says “Thanks for calling. Goodbye.” and ends the call with the end_call system tool.

    Why the desk ends the call. ElevenLabs re-sends the entire conversation, typed digits included, to whichever agent speaks next; we proved this live for transfers between Custom-LLM agents. The main agent's hosted model is not behind the vault, so the desk must never hand back to it. It ends the call, or could transfer to a person. transfer_to_agent has no option to drop history today.

    Full conversation (Custom LLM). Every turn goes through the vault to Airlock, the merchant's own model handles the conversation, and the conversation continues after the payment. That is the design the step-by-step above walks through.

    Payment desk recommendedFull conversation (Custom LLM)
    Conversation modelElevenLabs-hostedThe merchant's own
    Vault in the pathPayment turns onlyEvery turn
    Extra modelNoneThe merchant's model, behind Airlock
    After paymentCall ends, or goes to a personConversation continues
    Latency added to normal turnsNoneThe vault hop on every turn (~0.65 s median)

    Who holds which key

    HolderHoldsCanCannot
    ElevenLabs AgentsThe inbound proxy key, stored as a workspace secret and sent as a request headerCall the inbound proxyRead or create tokens directly
    Vault inbound transformtoken:create onlyTurn keypad entries into tokensRead a token back
    Airlocktoken:delete onlyName tokens in an outbound charge request; delete them after each attemptRead a token, or use one with any proxy
    Detokenizing vault applicationtoken:use, attached only to the fixed-destination outbound proxiesResolve token expressions inside the vault, on the way to the gatewayBe reached any other way
    Vault outbound proxiesA fixed gateway destination eachBuild the gateway body and post itSend card data anywhere else
    Gateway (NMI, Authorize.net)Its own vault: NMI Customer Vault, Authorize.net customer profilesStore saved cards and bank accounts—

    Tokens expire after 10 minutes and are deleted after each attempt. Saved cards and accounts never become long-lived vault tokens: they go to the gateway's own vault, so the only durable copy of a card lives where it would live anyway.

    04Why a middleware at all?

    What a vault transform can't — or shouldn't — do

    A vault transform can rewrite a request. It can't run a payment. Airlock exists for five jobs:

    1. State across turns. Which step the caller is on, the token ids, attempt counts and settled orders — the double-charge guard. Vault transforms are stateless.
    2. The amount comes from the merchant. Never from the model, never from the caller.
    3. Model routing. Ordinary turns stream through to the model; Airlock intercepts start_payment and runs the capture itself.
    4. A small card environment. About 200 lines run inside the vault. All business logic lives outside it, on tokens only.
    5. Observability without card data. The event stream that drives the live diagram above carries step names, brands, last fours and timings — never a card number.

    Option B: vault-only

    The alternative is to point the inbound proxy straight at the model and run the whole capture inside the transform. We didn't choose it. All business logic would become card-environment code; state would have to be rebuilt from the re-sent history on every turn; transforms have a 10–30 second limit; and every conversational turn would be buffered.

    When ElevenLabs ships native secure capture, jobs 1–4 move into the platform and Airlock disappears.

    05Proof

    What has to be true, and how we checked

    125
    automated tests pass
    10/10
    live vault tests pass on the Basis Theory tenant
    0
    card-data hits in a leak scan of every log and capture
    InvariantHow it was tested
    No card digits arrive at AirlockEvery recorded step below shows what Airlock actually received; keypad entries arrive only as [[airlock:kp …]] markers. Leak scan: 0 hits.
    If they did, Airlock refuses themA tripwire detector at Airlock; unsigned requests get HTTP 401. Covered by the automated tests.
    Nothing card-like reaches the model240 generated card numbers in 6 formats, including spoken words; the Python and JavaScript detectors are tested for parity.
    Airlock cannot read or use a tokenLive: GET /tokens/<id> with Airlock's key returns HTTP 403; so does the create-only key. Airlock's key holds token:delete only.
    Card data leaves the vault only to the gatewayLive: a token steered into zip or an echo field is refused with HTTP 400; a caller cannot redirect the fixed-destination proxy — the reply still came from NMI; Airlock's key cannot send card data to an arbitrary URL through an ephemeral proxy (HTTP 403).
    The security code is gone after authorizationLive: Airlock's key deletes the token (HTTP 204) and a deleted token can no longer be charged (HTTP 400). Tokens also expire after 10 minutes.
    The capture is deterministicCapture state-machine and eCheck tests in the automated suite (125 tests, including 240 generated card numbers in 6 formats); scripted sentences in every recorded run.
    Every component fails closedUnsigned requests: 401. Card data in the wrong field: 400. A deleted token: 400.
    Logs hold no card dataLeak scan of every log and capture: 0 hits.

    Built-in protections

    • Airlock's key can only delete tokens. Detokenization lives on a separate vault application attached only to the fixed-destination outbound proxies, so even a key leaked from the Airlock host cannot send card data to another URL (tested: HTTP 403).
    • Card numbers are detected however they are written — separated by spaces, dashes, commas or slashes, typed in other digit scripts, or spoken as words.
    • A charge always settles. If the call drops mid-charge, the outcome is still recorded.
    • One order cannot be charged twice in a call.
    • Unclear gateway outcomes are never reported as “no charge”.
    • Confirming a payment requires keypad 1 or a clear yes.

    Evidence

    Recorded end-to-end runs against the sandboxes. Open a step to see what the caller typed (redacted here), what Airlock actually received, and what the agent said. Open a charge to see the exact request Airlock sent the vault — token expressions only — and the reduced gateway reply.

    Loading evidence…

    06What we measured

    The vault costs about two-thirds of a second

    648ms
    median keypad turn through the vault (13 samples)
    <20ms
    Airlock's own handling
    4–5s
    one charge, end to end
    ~10.8s
    added by a cold vault runtime, first request

    The 648 ms median is the full round trip from our test driver through Basis Theory to Airlock on DigitalOcean and back. Almost all of it is the vault hop.

    The vault proxy buffers streamed replies. Time-to-first-byte equalled total time in every sample, so a “one moment” sentence streamed by the model cannot be heard early — the caller would hear it together with the result.

    So the agent uses ElevenLabs' native soft-timeout fillers instead: “One moment.” at 2.0 seconds, then “Still working on that, thanks for waiting.”, then “Almost there.” They are static text on purpose. The alternative, model-generated fillers, would hand recent conversation context — which may contain keypad turns — to another model.

    Cold starts are real. A cold vault runtime added about 10.8 seconds to the first request. Airlock keeps the vault runtimes warm with a ping answered inside the vault, and pre-warms the outbound proxy as soon as the caller reaches the confirm step.

    Timeouts need room. ElevenLabs' default cascade_timeout_seconds is 4 seconds (range 2–15). A charge can take longer than that, so the demo agent is set to 15 seconds, and Airlock's capture guard prevents a double charge if ElevenLabs retries the turn.

    Turn times for the eCheck call through the live agent.

    07What the tests changed

    Seven things we only learned by running it

    1. Sub-agent isolation doesn't work. The whole history, typed digits included, is re-sent to the Custom LLM on every turn, even after a transfer to another agent.
    2. NMI's duplicate check ignores the order id. The same card and amount are blocked for about 20 minutes regardless of order. Airlock now tells the caller “that payment already went through” instead of a generic failure.
    3. Authorize.net replies start with a UTF-8 byte-order mark. The reply has to be read with that in mind before it can be parsed.
    4. Authorize.net's sandbox rejects eCheck SEC code TEL (error 246; it must be enabled per merchant). The sandbox uses PPD as a stand-in.
    5. Both gateways require the account-holder name for eChecks. The agent asks for it in conversation before the keypad steps.
    6. Spoken card numbers are cut at the vault. When a caller reads a card out loud, the number is removed from the sentence and the rest of what they said survives.
    7. Timestamps looked like cards. Values such as 1791292673.7754595 tripped an early detector. Dots are no longer treated as separators.

    08What it costs and what it means for PCI

    Free software, a paid vault, a smaller audit

    Airlock itself is free, Apache-2.0 and self-hosted. The vault is the cost.

    OptionPriceNote
    Basis Theory$995/month, includes 20,000 tokens; $0.05/token afterTest tenants are free. Used for this demo.
    Evervault$995/month + usage
    VGSFrom $1,000/month
    Twilio Pay ConnectorsFrom $0.15/transactionFor comparison: the caller leaves the agent to pay.

    A payment uses about three tokens, so the Basis Theory base plan covers roughly 6,600 payments a month. Spread over volume:

    Payments per monthVault cost per payment
    100~$10
    1,000~$1
    6,000~$0.17

    PCI scope

    SAQ A (r1, January 2025) can cover mail- and telephone-order merchants whose account-data handling is fully outsourced to PCI DSS compliant providers. In Airlock's design, card data exists only at ElevenLabs, the vault and the gateway. But Airlock directs the payment flow, so an assessor will likely treat it as security-impacting: expect a reduced set of controls, not zero.

    Software isn't PCI compliant; deployments are assessed. This is engineering guidance, not legal advice.

    09Open source

    Run it yourself

    Airlock is Apache-2.0 at github.com/LiftedHoldings/airlock. The provisioning scripts create the vault keys, transforms and proxies, and the ElevenLabs agent.

    # 1. run Airlock
    docker compose up -d
    
    # 2. create the vault keys, inbound transform and outbound proxies
    python cli/provision_basis_theory.py
    
    # 3. point an ElevenLabs agent at the vault inbound proxy
    python cli/provision_elevenlabs_agent.py

    The eight ElevenAgents settings that matter

    SettingValue
    Custom LLM URLThe vault inbound proxy
    Request header BT-PROXY-KEYFrom a workspace secret
    dtmf_input_settings.hash_terminatortrue
    dtmf_input_settings.dtmf_input_timeout5.0
    dtmf_input_settings.redact_inputtrue
    record_voicefalse
    cascade_timeout_seconds15
    Keypad inputEnabled

    10A recommendation for the ElevenLabs product team

    Two changes that would retire the workaround

    Everything above exists to stop one thing: keypad digits becoming a model turn. The platform could do that directly.

    1. Secure keypad capture

    • A payment tool arms secure capture.
    • While armed, keypad entries are held as secret values and never become a model turn. The platform speaks the configured prompts.
    • When the fields are complete, it calls the tool's webhook with the secret values substituted server-side — as secret__ dynamic variables already are in tool headers.
    • The model receives only approved or declined, the brand and the last four.
    • The values are dropped when the webhook returns, and never reach transcripts, logs or analysis.

    The webhook can post straight to NMI or Authorize.net, so the gateway's own vault becomes the only token store. No proxy, no $995 a month, no middleware.

    Native secure capture also lets the agent keep ElevenLabs' hosted model: no Custom LLM, no vault, no extra model box.

    Wireframe of the proposed setting. The first two rows exist today; the highlighted rows are the proposal.
    {
      "conversation_config": {
        "conversation": {
          "dtmf_input_settings": {
            "dtmf_input_timeout": 5.0,
            "hash_terminator": true,
            "redact_input": true,
            "secure_capture": {
              "enabled": true,
              "armed_by_tool": "collect_card_payment",
              "fields": ["card_number", "expiration", "security_code"],
              "model_view": "brand_and_last_four"
            }
          }
        }
      }
    }

    Why it is small

    What the feature needsStatus
    Buffer keypad digits until #exists DTMF input
    Know a turn came from the keypadexists DTMF source medium
    Keep a value away from the modelexists secret__ dynamic variables
    Call an API with stored credentialsexists webhook tools + secrets
    Redact the entry in historyexists redact_input
    Route buffered digits to a tool instead of the model turnmissing this is the feature

    2. A transfer that leaves keypad turns behind

    Let transfer_to_agent hand off without keypad turns (or with them redacted in the live context). That alone would let a payment desk hand back to the main agent.

    Until then, Airlock is the working pattern you can deploy today.