
Subscribe
Tags
Voice AI
AI Agents
Compliance & Security
Developer Tutorials
The card number nobody should have heard
In Part 1, we talked about data that wanders into your AI agent’s context uninvited. An account number tagging along on a tool response. An auth token that slipped in because the query happened to scoop it up on the way out. Annoying, a little risky, and very fixable!
Payment data is a different kind of guest. It doesn’t sneak in through the side door. It gets a personal invitation, a reserved seat, and someone to announce its name at the door.
It usually starts with a prompt that sounds perfectly sensible: collect the card number, the expiration date, and the CVV, then read the number back so the caller can confirm it. The agent, ever the obliging host, does exactly that. The caller recites sixteen digits. The model takes them in and reads them right back, one digit at a time, on a live call, like it’s calling out winning lottery numbers. Now those sixteen digits have made themselves at home in the model’s context, the call recording, the transcript, and every request your agent fires off to its LLM provider for the rest of the call.
So the prompt did exactly what it said but the real trouble is who it asked. The agent that was running the conversation is the last one who should be anywhere near the card.
Why the AI is the wrong place for a card number
Once a card number lands in the context window, it doesn’t leave when the conversation moves on. It stays put for the rest of the call. Every word the model says next gets generated with those sixteen digits sitting right there in the room. And models hallucinate. So a model that has seen a card number can cheerfully produce a slightly mangled version of it later, tucked into a summary or a confirmation line, with no intent at all.
Then there’s the paper trail. Everything the model touches gets written down somewhere: conversation logs, post-call summaries, the context you ship off to your LLM provider. You could go back and scrub all of it after the fact, but that runs into the same wall as Part 1. But cleaning the log only cleans the log.
So the fix is the same, too. Don’t let the data into the model’s context in the first place. For payments, that means the AI never hears the card at all.
Hearing the card number vs. handling the payment
Picture paying at a restaurant. Your server brings the card terminal to the table, hands it over, and politely steps back. You tap or insert your card and punch in your PIN. The terminal has a quick private chat with the bank. A few seconds later, the server gets exactly one piece of news: approved or declined.
The server ran the whole show. They knew what you ordered, what you owed, and when it was time to settle up. At no point did they need your card number or your PIN to do any of that. Their job was to know the bill got paid, not how it got paid.
Your AI agent should work the same way. The agent runs the conversation and decides when it’s time to pay. The platform collects the card. Your payment processor runs the charge. The agent gets the outcome and picks the conversation right back up.
That’s what SignalWire Markup Language (SWML) pay is for.
The wrong way to handle payments with AI
This is the version that feels fine until someone reads the logs:
And it works! The agent collects the card, reads it back, and the caller walks away happy. It also drops the full card number and CVV straight into the model’s context, then asks a language model to recite cardholder data out loud on a live call. No amount of redaction afterward makes that safe. By then, the card has already been heard.
The right way to handle payments with AI: agent handoff
Start with the prompt. The agent's job is to run the order and know when it's time to pay. It should never ask for card details, and it should know what to do if a caller starts reading them out anyway:
That last line is doing real work. Until the payment step kicks in, the agent is still listening. A caller who helpfully reads their card number out loud walks it right back into the model’s context. The prompt is your first line of defense.
Next up, pricing. A calculate_total function prices the order from your own menu, and the total goes into metadata, not just the response. The model sends item names. It never gets a say in prices:
When the caller is ready to pay, the agent calls collect_payment. That function hands the call over to pay, and it pulls the amount from metadata, not from whatever the model remembers:
Here's what happens next. The true passed to FunctionResult tells the agent to say its line first, so the caller hears "You'll enter your card details using your phone's keypad." Then the agent stops listening and waits. pay prompts the caller to enter their card on the keypad and sends the details to your payment connector. The set step writes the agent's follow-up line, and the agent picks back up with one piece of new context: the payment status.
These examples use a plain Express server to keep them readable, and need Node 22 or later. In production, the Agents SDK's built-in server handles request security for you. Running SWML from functions is allowed by default; if you've turned off swaig_allow_swml, the pay step will be skipped.
Three details that keep the card number out of the model
The amount comes from the backend, not the conversation. The model gets to decide when to charge. It doesn’t get to decide how much. A caller who sweet-talks the agent into “just charge me five bucks” won’t get very far, because the agent was never holding the number in the first place. Same goes for prices: the model names the items, and your server decides what they cost.
pay runs first, then the follow-up line. SWML fills in variables like ${pay_result} at the moment a step runs, not before it. If the set step came before pay, there would be no result to fill in yet, and the agent would come back without knowing how the payment went. So order really matters here.
The model learns the outcome, not the details. Once pay wraps up, the model knows exactly two things: the line your function said before payment started, and the ai_response text with ${pay_result} filled in. It knows whether the payment went through but it has no clue what card was used, because nobody ever told it.
The one-line mistake to avoid
The ai_response text can reference any SWML variable. That includes pay_payment_results, which holds detailed payment data. The card number and CVV are masked there, but the expiration date and postal code are sitting in plain sight.
So this one innocent-looking line would undo everything above:
Whatever goes into ai_response goes into the conversation and the logs. Same story for your post-call summary prompt, and for any later functions set up to receive call variables, since pay_payment_results hangs around on the call until it ends. After all that work showing the card the side door, this would be holding the front door wide open.
The agent only needs to know whether the payment worked. ${pay_result} covers exactly that.
What the model knows, start to finish
Here’s the same call from the model’s point of view.
It helped the caller build an order and read them the total. When they were ready to pay, it called collect_payment and told them to use their keypad. Then it waited. The next thing it heard was the payment status, so it confirmed the order and gave a delivery time.
As far as the AI is concerned, that’s the whole call. The card never appears in its context or its conversation log.
Your backend, meanwhile, has what it needs. The order ID and total are sitting in metadata, and the card details went to your payment connector and nowhere else. That makes the connector the one part of this setup that actually handles card data, so keep it separate from the server that runs your agent’s functions.
Where payment security goes next: transcription, translation, and streaming
This post is about one thing: keeping payment data out of your AI agent. That's where most of the risk lives in an AI-driven payment flow, and it's where the metadata pattern does its best work.
A call can have more going on than the agent, though. If your calls also use live transcription, translation, or audio streaming, those deserve their own look. We'll cover payment security across the whole call in Part 3.
A short checklist
Collect cards with pay, never with speech in the AI conversation. Tell your agent never to ask for card details and to redirect callers who start reading them out. Take prices and the charge amount from the backend state, never from the model's arguments. Run pay first, then set the agent's follow-up line, and keep that line to ${pay_result}. Then run the flow in your processor's test mode and read the agent's conversation log end to end. If you can't find a single digit of card data in it, you built it right.
Build a payment flow the model never hears
Have questions about adding the pay handoff to your own agent, or keeping card data out of your logs? Bring them to the SignalWire Discord, where the developer community and the SignalWire team can help you work through it.

Why should an AI agent never hear a credit card number?
Once a card number enters the model’s context, it stays there for the rest of the call and flows into transcripts, recordings, and every request sent to your LLM provider. A model that has seen a card can also repeat a mangled version later in a summary or confirmation line. Keeping the card out of context is the only reliable fix.
What happens if an AI voice agent reads a card number back to confirm it?
The digits enter the model’s context, the call recording, the transcript, and every downstream LLM request for the rest of the call. Redacting the logs afterward does not solve this, because the model already heard the number and may reproduce it elsewhere.
How do you collect a payment without the AI hearing the card?
Hand the call off to a dedicated payment step. The agent decides when to charge, then a platform feature like SWML pay collects the card over the keypad and sends it to your payment connector. The agent picks the conversation back up knowing the outcome, not the digits.
Can you scrub card data from AI logs after the call?
Cleaning the log only cleans the log. The card still passed through the model’s context and every LLM request made during the call, and the model may have repeated it somewhere else. The safer approach keeps the card out of context from the start.
What is the most common mistake that leaks payment data back into the AI?
Passing detailed payment results into the agent’s follow-up text. A variable that holds the full results still exposes the expiration date and postal code. Keep the follow-up line to the payment status alone, which tells the agent whether the charge worked and nothing more.
Related Resources






