AWS Builder Center
Previewing Amazon Connect Agentic Voice in the Browser (No Phone Calls Required)

Previewing Amazon Connect Agentic Voice in the Browser (No Phone Calls Required)

A small web app that lets you paste text, pick an Agentic Voice model, and hear it instantly. Built with StartWebRTCContact, the Amazon Chime SDK, and CDK.

In July 2026, Amazon Connect Customer added Agentic Voice (Amazon agentic voice) as a voice provider. I wrote a summary of the update itself in a separate article (in Japanese): https://qiita.com/yosuke-suzuki/items/912583b37b9bba364403
When I moved on to actually tuning announcement scripts with it, the first thing I hit wasn't a bug. It was the preview workflow.
With Polly-based voices like Kazuha and Tomoko, you can paste any text into the Amazon Polly console and hear it right away. I looked for an equivalent for the Agentic Voice models and, as of this writing, couldn't find one in the official documentation. The only way to hear your own text is to rewrite the Play prompt block in a contact flow, publish the flow, and call the assigned phone number from your own phone. Moving a single comma costs you all three steps, every time.
I wanted the Polly console experience for Agentic Voice, so I built a preview web app. Paste text into the browser, pick a voice, and it reads the text back on the spot. No phone call, no flow update.
Code is on GitHub: https://github.com/4suke-sun/ACC_AgentVoicePreview
This post covers what the app does, how it's put together, and the implementation details, including two values I couldn't find documented anywhere and had to reverse engineer from a live instance.
Note: Everything in this post is current as of August 2026. Check the official documentation for the latest information.

What changed

The short version: comparing Agentic Voice models now happens entirely in the browser.
BeforeAfter
Changing the textRewrite the prompt text and publish the flowPaste text into the browser
ListeningCall a phone number and listenPlay it in the browser on the spot
Switching voicesChange the flow's voice setting and republishPick a voice on the screen
Here's a short demo video: https://www.youtube.com/watch?v=D-UxLtmR4uM

The edit, publish, dial loop

To hear a script read by an Agentic Voice model, the workflow used to look like this:
  1. Rewrite the text in the contact flow's Play prompt block
  2. Save and publish the flow
  3. Call the assigned phone number from your phone
  4. Listen. If something bothers you, go back to step 1
Depending on the length of the text, one lap through this loop can easily take a few minutes. And when you're tuning the fine details, how a pause changes the phrasing, how numbers and alphabet characters get read out, you go around this loop a lot.
So I decided to build the "type text, get speech back" experience that the Polly console gives you, but for Agentic Voice.

What it's built from

Here are the main pieces:
Technology / ServiceRole
Amazon Connect Customer (with Agentic Voice)Speech synthesis. A preview flow reads the text aloud
StartWebRTCContact APICreates one new WebRTC contact per playback
Amazon Chime SDK JSReceives and plays the WebRTC audio in the browser
Lambda + API Gateway (HTTP API)Backend that calls the Connect APIs
S3 + CloudFrontServes the frontend (Vite + TypeScript)
AWS CDKIaC for everything, including the contact flow
The prerequisites are in the official configuration guide :
To use Amazon Connect agentic voice features, make sure Amazon Connect Customer is enabled for your instance. Amazon Connect agentic voice is the default voice provider for Amazon Connect Customer.
In other words, to select Agentic Voice as the voice provider, your instance needs Amazon Connect Customer enabled.
There's also a regional constraint. As of August 2026, Agentic Voice is available in nine regions: N. Virginia, Oregon, Frankfurt, London, Sydney, Seoul, Tokyo, Singapore, and Canada (Central). I ran everything in this post in the Tokyo region (ap-northeast-1) on August 2, 2026.

Architecture

Here's the system diagram:
The only thing handed to the browser is the temporary WebRTC join information. No AWS credentials and no ContactFlowId ever reach the frontend. The frontend only deals with logical IDs like ja-jp-aiko, and the mapping from a logical ID to an actual flow lives in an allowlist inside the Lambda.

Deploying it

Prerequisites:
  • An Amazon Connect Customer instance where Agentic Voice is available (see the region note above)
  • Node.js 20 or later
  • An AWS account that has been CDK bootstrapped
Clone the repo, pass your Connect instance ID, and deploy:
1
2
3
4
5
6
7
8
9
10
git clone https://github.com/4suke-sun/ACC_AgentVoicePreview.git
cd ACC_AgentVoicePreview
npm ci

# Build the frontend (required before deploy)
npm run build -w @avp/frontend

# Deploy (creates the contact flow, Lambda, API, and hosting)
cd packages/infra
npx cdk deploy -c connectInstanceId=<your Connect instance ID>
The WebUrl in the stack output is the preview screen. Local development, CORS settings, and other options are covered in the repository README.

Checking that it works

I opened the WebUrl from the deploy output in a browser. Type the text you want to hear, pick a voice, press play, and the selected voice model reads it back.
TestExpected behaviorResult
Playback with AIKO / REN / YUMIKOThe selected voice plays from the browserPass
Voice resolutionGlobalVoice in the flow log matches the voice name that was passedPass
Engine valueGlobalEngine in the flow log is connect:agenticPass
Auto disconnect after playbackThe contact ends by itself when the speech finishesPass
Idempotency on repeated clicksNo duplicate contact is created for the same previewIdPass
Since you can switch voices and replay the same text back to back, comparing voices got faster, which was the whole point. There's still a small wait before the speech starts, but compared to dialing a phone number it's nothing.

One playback, one WebRTC contact

Now for the internals. This section covers the design decisions that mattered and the one spot where I got stuck.
Every press of the play button creates one new WebRTC contact via StartWebRTCContact. The text to read goes into the contact attributes as previewText, and the voice name as previewVoice. The flow reads those attributes, speaks the text, and a Disconnect block ends the contact.
StartWebRTCContact was originally built for placing calls from apps and websites. The steps for wiring it to Chime SDK JS and playing audio in the browser are in the in-app, web, and video calling documentation . I just repurposed it for previewing.
Starting a preview looks like this in the backend Lambda:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Start a preview: create one new contact via StartWebRTCContact
* - Pass previewId (a UUID) as ClientToken to prevent duplicate contacts on repeated clicks
* - The text and voice name travel to the flow as contact attributes
*/

const previewId = randomUUID();

const output = await getConnectClient().send(
new StartWebRTCContactCommand({
InstanceId: config.instanceId,
ContactFlowId: contactFlowId,
// Idempotency key so a retried request doesn't create a second contact
ClientToken: previewId,
ParticipantDetails: { DisplayName: 'Agentic Voice Preview' },
Attributes: {
previewText: parsed.text,
previewId,
// The voice name the dynamic flow's Set voice block resolves
previewVoice: voice.connectVoiceId,
},
}),
);

// Return ConnectionData (Meeting / Attendee) to the frontend as is
const body: StartPreviewResponse = {
previewId,
contactId: output.ContactId,
connectionData: {
meeting: output.ConnectionData.Meeting,
attendee: output.ConnectionData.Attendee,
},
};
The ClientToken gives you request idempotency. The StartWebRTCContact API reference  says the token is valid for 7 days and that if a contact was already started with the same token, the API returns that contact's ID. So even if the network flakes out and the client retries, the same previewId never produces two overlapping voices.
Contact attributes have a limit of 32,768 UTF-8 bytes across all key-value pairs, so the input text is capped before it ever gets there:
1
2
3
4
5
/** Max characters (code points), based on the Play prompt limit */
export const MAX_TEXT_LENGTH = 3000;

/** Max UTF-8 bytes for previewText, based on the 32 KB contact attribute limit */
export const MAX_TEXT_BYTES = 12000;
On the browser side, the returned Meeting and Attendee go straight into Chime SDK JS's MeetingSessionConfiguration. Since this is preview only, the app never asks for microphone access.
1
2
3
4
5
6
7
/**
* Create a Chime SDK session from StartWebRTCContact's ConnectionData
*/

const logger = new ConsoleLogger('AgenticVoicePreview', LogLevel.WARN);
const configuration = new MeetingSessionConfiguration(data.meeting, data.attendee);
const deviceController = new DefaultDeviceController(logger);
return new DefaultMeetingSession(configuration, logger, deviceController);
The stop button calls StopContact. If the contact has already ended, Connect returns ContactNotFoundException, and I treat that as success. Pressing stop right after the flow has finished speaking and disconnected is a completely normal sequence of events, not an error.

The preview contact flow

The flow itself is deliberately simple, since all it has to do is speak:
Voice switching happens in the Set voice block. I only targeted Japanese voices this time, so the language code is a fixed value and only the voice is dynamic. If you also pass the language code from Lambda, the same flow extends to the other languages Agentic Voice supports.

The part that isn't documented: voice names and the engine value

The implementation needs two values: the Agentic Voice voice names (AIKO / REN / YUMIKO) and the engine value connect:agentic. As of this writing I couldn't find either in the official documentation, so I pulled both out of a live instance.
For the engine value, I built one flow in the console with Agentic Voice selected, exported it, and opened the JSON. TextToSpeechEngine contained connect:agentic.
For the voice names, the Set voice block documentation  says "For the voice, simply pass the name of the voice (for example, Joanna or Hala)", so I knew the format was just the plain voice name. What I didn't know was which names are actually valid for Agentic Voice. Those came from the flow logs. With flow logging enabled on the instance, run this query in CloudWatch Logs Insights:
1
2
3
fields @timestamp, ContactId, Parameters.GlobalVoice, Parameters.GlobalEngine
| filter ContactFlowModuleType = "SetVoice"
| sort @timestamp desc
In the flow logs, the block shows up as SetVoice with parameters GlobalVoice and GlobalEngine. If you see AIKO and connect:agentic there, the voice name you passed through contact attributes was resolved by the Set voice block the way you intended. The Play prompt block's log entry also recorded the Voice used for synthesis.
Warning: This is a recent update, so these values may change. AIKO / REN / YUMIKO are the names I confirmed in the console and flow logs on August 2, 2026. When adding voices, check the exact names in the console's Set voice block first.

Known limitations

This is an experiment tool, and I kept it that way on purpose:
  • Input text is stored in plaintext in CloudWatch Logs. Flow logging is enabled, so previewText gets recorded as is. Don't paste anything confidential or personal. (The Lambda's application logs only record the character count, not the text itself.)
  • No authentication. Anyone who knows the URL and the API endpoint can use it. If you want something closer to production, fork it and add auth or IP restrictions.

Wrapping up

That's the design: one StartWebRTCContact call per playback, and a single dynamic flow that resolves the voice name from contact attributes. The publish-and-dial loop is gone, and comparing scripts across voices went from minutes per iteration to seconds.
Cost-wise, Agentic Voice is pricier than Polly, but that's equally true when testing over a real phone call, so I can live with it.
Next I want to try adding non-Japanese locales, and maybe a UI that plays the same text through several voices in a row for side-by-side comparison.

References

  • Update summary article (in Japanese): https://qiita.com/yosuke-suzuki/items/912583b37b9bba364403
  • Agentic voice configuration guide: https://docs.aws.amazon.com/connect/latest/adminguide/agentic-voice-configuration-guide.html
  • Availability by Region: https://docs.aws.amazon.com/connect/latest/adminguide/regions.html
  • Flow block: Set voice: https://docs.aws.amazon.com/connect/latest/adminguide/set-voice.html
  • StartWebRTCContact API reference: https://docs.aws.amazon.com/connect/latest/APIReference/API_StartWebRTCContact.html
  • Set up in-app, web, and video calling: https://docs.aws.amazon.com/connect/latest/adminguide/config-com-widget2.html
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article