01 — AMAZON BEDROCK AGENTCORE

TURN Server for Amazon Bedrock AgentCore WebRTC

AgentCore Runtime requires a TURN relay for WebRTC media. Metered is a managed third-party TURN option: relays in 31+ regions, TURN over TLS on port 443 and credentials from a REST API, with no relay servers or signaling channels for you to run.

Get Started Free
AWS docs
TURN required
At launch
14 AWS Regions
Metered free tier
500 MB / month

02 — WHY TURN

Why AgentCore Runtime needs a TURN relay for WebRTC

WebRTC on AgentCore Runtime runs in VPC network mode, and the runtime can't be assigned a public IP. AWS NAT gateways use symmetric NAT, so a STUN-only connection can't succeed either. That's why AWS requires a TURN relay for media between the client and the agent.

VPC NETWORK MODE

Your agent lives in a VPC

WebRTC on AgentCore Runtime requires VPC network mode, with egress through a NAT gateway.

AWS: WebRTC on AgentCore Runtime

NO DIRECT PATH

No public IP, no STUN

The runtime can't take a public IP, and symmetric NAT on AWS NAT gateways stops STUN-based direct connections.

AWS Machine Learning Blog, March 2026

TURN REQUIRED

Media goes through a relay

AWS requires a TURN relay for media traffic, and your VPC must allow outbound UDP to the TURN endpoints.

AWS: TURN requirements

03 — SETUP

Connect Metered TURN to an AgentCore WebRTC agent

Four steps, starting from the WebRTC voice agent in AWS's AgentCore samples.

Code adapted from awslabs/agentcore-samples, © Amazon.com, Inc. or its affiliates, Apache License 2.0.

STEP 1

Create a credential and store its API key

Create a TURN credential in the Metered dashboard or with the Create Credential API. Store the credential's API key in AWS Secrets Manager and give the agent's execution role secretsmanager:GetSecretValue on that secret.

The API key only returns this credential's ICE servers, so it is safe in front-end code. Keeping it in Secrets Manager rather than the runtime's environment variables, which the AgentCore control API returns in plain text, lets you rotate credentials without redeploying.

store-api-key.sh
# metered-turn-credential.json holds {"apiKey": "YOUR_CREDENTIAL_API_KEY"}.
# Reading it from a file keeps the key out of your shell history.
aws secretsmanager create-secret \
  --name metered/turn-credential \
  --secret-string file://metered-turn-credential.json

STEP 2

Fetch the ICE servers in your agent

Add a small module that reads the API key and calls Metered's Get Credential API. It returns STUN plus TURN over UDP, TCP and TLS, ready for RTCConfiguration.

Then make three edits to the sample's agent/bot.py: import the module in place of the sample's managed-TURN helper, drop the signaling-channel setup from lifespan, and fetch Metered's ICE servers in the ice_config and offer handlers. The ice_config handler becomes async, so invocations() has to await it.

Fetch per request, not at startup. Add METERED_DOMAIN and METERED_SECRET_ID to the runtime's environment variables in the sample's deploy script (neither is a secret). The sample's channel-name setting and its managed-TURN IAM policy are no longer needed.

aiortc uses the first TURN URL in the list. With Metered's list that's TURN over UDP on port 80, which is what VPC mode is set up for.

agent/metered_turn.py
# Metered TURN for an AgentCore WebRTC agent (example)
import asyncio, json, os, urllib.parse, urllib.request

import boto3
from aiortc import RTCConfiguration, RTCIceServer

METERED_DOMAIN = os.environ["METERED_DOMAIN"]  # e.g. yourapp.metered.live
SECRET_ID = os.environ["METERED_SECRET_ID"]    # e.g. metered/turn-credential
_secrets = boto3.client("secretsmanager", region_name=os.environ.get("AWS_REGION"))


def _fetch_ice_servers() -> list[dict]:
    secret = json.loads(_secrets.get_secret_value(SecretId=SECRET_ID)["SecretString"])
    query = urllib.parse.urlencode({"apiKey": secret["apiKey"]})
    url = f"https://{METERED_DOMAIN}/api/v1/turn/credentials?{query}"
    with urllib.request.urlopen(url, timeout=5) as resp:  # raises on HTTP errors
        return json.load(resp)  # [{"urls", "username", "credential"}, ...]


async def get_ice_servers() -> list[dict]:
    # boto3 and urllib block, so keep them off the event loop
    return await asyncio.to_thread(_fetch_ice_servers)


def rtc_configuration(servers: list[dict]) -> RTCConfiguration:
    return RTCConfiguration(iceServers=[
        RTCIceServer(urls=s["urls"], username=s.get("username"), credential=s.get("credential"))
        for s in servers
    ])
agent/bot.py (changes)
import metered_turn  # replaces the sample's managed-TURN helper module


@asynccontextmanager
async def lifespan(app: FastAPI):
    # The sample's signaling-channel setup line goes: Metered needs no channel
    yield
    for pc in peer_connections.values():
        await pc.close()


@app.post("/invocations")
async def invocations(request: dict, background_tasks: BackgroundTasks):
    action = request.get("action")
    if action == "ice_config":
        return await _handle_ice_config()  # now async, so await it
    # ...offer, ice_candidate and disconnect stay as they are


async def _handle_ice_config():
    # The browser gets the ICE server list; it never needs your Metered API key
    return {"iceServers": await metered_turn.get_ice_servers()}


async def _handle_offer(data, background_tasks):
    servers = await metered_turn.get_ice_servers()
    pc = RTCPeerConnection(metered_turn.rtc_configuration(servers))
    # ...the rest of the sample's offer handling stays the same

STEP 3

Connect from the browser

The sample's test page calls InvokeAgentRuntime straight from the browser with pasted AWS credentials. In production, put that call behind your own backend: it signs the request and reuses one session ID for every step of a call.

The browser asks for the agent's ICE config, then builds its peer connection from that list. You don't need a signaling channel resource: the offer, the ICE candidates and the hang-up all go through the invoke API.

browser.js
// invokeAgent(action, data) is your call to your own backend,
// which signs InvokeAgentRuntime for this session.
const { iceServers } = await invokeAgent("ice_config");
const pc = new RTCPeerConnection({ iceServers });

// To test the relay path on its own:
// new RTCPeerConnection({ iceServers, iceTransportPolicy: "relay" })

// The offer, ICE candidates and disconnect use the same
// actions as the sample.

STEP 4

Open egress from your VPC

Run the agent in private subnets whose default route points at a NAT gateway, then allow these outbound ports in the agent's security group.

Security groups can't match hostnames, so the destination is 0.0.0.0/0 on these ports. No inbound rules are needed. An aiortc agent needs only UDP 80 and TCP 443; keep the other two if you change libraries or reorder the ICE list.

Outbound rules for an AgentCore WebRTC agent that uses Metered TURN
PROTOCOL PORT USED FOR
UDP 80 TURN over UDP (the entry aiortc uses) and STUN
UDP 443 TURN over UDP on 443
TCP 80 TURN over TCP
TCP 443 TURN over TCP and TLS, HTTPS to your metered.live API, and the AWS APIs your agent calls (Secrets Manager, Bedrock, ECR, CloudWatch)

04 — PRODUCTION

Before you go to production

Rotate credentials on a schedule

Never create a credential at call setup: a new one takes up to two minutes to start working. Create a 48-hour credential once a day and switch your secret to it a few minutes later, once it is live. The rotation job keeps your account Secret Key in its own secret, which the agent's role can't read.

Rotation guide

Choose the relay region

Add a region to the ICE request to direct relays to one geography, such as Europe.

Region guide

Keep the API key out of logs

Log the status of the ICE request, never its URL or a traceback's local variables: the query string carries the credential's API key.

Using Pipecat on AgentCore?

The Pipecat example linked from AWS's AgentCore examples reads TURN settings from environment variables. Fetch Metered's ICE servers per session instead, since the runtime's environment variables are readable in plain text.

AWS: AgentCore WebRTC examples

05 — PLANS

Start free. Scale to enterprise.

Prototype your agent on the free trial, then move to the plan, or the contract, that fits your traffic.

STARTUPS AND GROWING TEAMS

Self-serve, live in minutes

  • 500 MB of TURN every month on the free trial. It renews monthly and needs no credit card.
  • Paid plans from $99/month as your agents take on real traffic.
  • Create credentials and fetch ICE servers from the REST API.
  • Docs your coding assistant can read, from our Build with AI page.
  • Email support on every plan, including the free trial; priority support on the Business plan.

06 — FAQ

AgentCore WebRTC TURN: FAQ

Does Amazon Bedrock AgentCore need a TURN server for WebRTC?

Yes. AWS's AgentCore WebRTC documentation requires a TURN relay for media between the client and the agent, and your VPC must allow outbound UDP to the TURN endpoints.

Can I use a third-party TURN provider with AgentCore?

Yes. AWS lists three options: its own managed TURN, a third-party managed TURN provider, or self-hosted TURN such as coturn on EC2 or ECS. Metered is a managed third-party option.

Do I need a signaling channel to use Metered TURN with AgentCore?

No. A signaling channel resource is only needed to get credentials from AWS's built-in TURN. With Metered, your agent fetches the ICE server list over HTTPS, and signaling runs through the AgentCore Runtime invoke API.

Why doesn't STUN work on AgentCore Runtime?

The runtime can't be assigned a public IP, and AWS NAT gateways use symmetric NAT, which stops STUN-based direct connections. AWS's guide to voice agents on AgentCore Runtime recommends the TURN relay path for that reason.

Which ports does my AgentCore VPC need to allow?

Outbound UDP 80 and 443 and TCP 80 and 443, through a NAT gateway. An aiortc agent uses the UDP port 80 entry; TCP 443 also carries TURN over TLS and the HTTPS call that fetches the ICE servers.

Which AWS Regions support AgentCore WebRTC?

AWS launched WebRTC for AgentCore Runtime on March 20, 2026 in 14 Regions: US East (N. Virginia), US East (Ohio), US West (Oregon), Canada (Central), Asia Pacific (Mumbai, Seoul, Singapore, Sydney and Tokyo) and Europe (Frankfurt, Ireland, London, Paris and Stockholm). Check AWS's announcement for the current list.

Does Metered TURN work with Amazon Nova Sonic and Pipecat?

Any agent that accepts a standard ICE server list can use Metered TURN, whatever model it runs. AWS's AgentCore examples include a Nova Sonic voice agent built on aiortc and a Pipecat agent on the SmallWebRTC transport, and both take one.

KEEP READING

Give your AgentCore agent a relay.

Start with 500 MB of TURN every month, free. No signaling channel, no TURN servers to run.