# Ephemeral keys trigger Realtime sideband 404 errors

**URL:** <https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002>\
**Category:** Bugs\
**Tags:** realtime, realtime-api, websocket\
**Created:** [September 9, 2026, 6:44am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002 "2026-09-09T06:44:42Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![akira\_numakura](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/akira_numakura/32/681488_2.png) [@akira\_numakura](https://community.openai.com/u/akira_numakura)\
**Post date:** [September 9, 2026, 6:44am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/1 "2026-09-09T06:44:42Z")

</div>

## Our setup

The client does WebRTC. Our Node server sits in the middle of the SDP exchange and then opens a **sideband WebSocket** — the server-side control connection described in the _Webhooks and server-side controls_ guide (`wss://api.openai.com/v1/realtime?call_id=...`) — to handle function calls on the server. The client never receives any OpenAI key; it only gets the answer SDP.

 ![openai_forum_post_diagram.en](https://us1.discourse-cdn.com/openai1/original/4X/2/d/d/2dde4044f69a22d8e2ea0dbbf7c5affe10396d27.png)

The two keys in the diagram are what this post is about:

| Variant | KEY A (creates the call in step 2) | KEY B (sideband in step 5) | How step 2 is sent |
| --- | --- | --- | --- |
| **A — what we ran until now** | ephemeral key from `POST /v1/realtime/client_secrets` | the same ephemeral key | `Content-Type: application/sdp`, body = SDP |
| **B — as shown in the guide** | ephemeral key | standard API key | same as A |
| **C — unified interface** | standard API key | standard API key | `multipart/form-data` with fields `sdp` and `session` |

## What happened

Variant A had been running unchanged in production since 2025-10. On **2026-09-09** , with no change on our side, step 5 started failing: the WebSocket handshake took about **5.5 seconds** and returned

```auto
HTTP/1.1 404 Not Found
{
    "error": {
        "message": "No session found for the provided call_id",
        "type": "invalid_request_error",
        "code": "call_id_not_found",
        "param": ""
    }
}

```

Step 2 kept returning 201 with a valid answer SDP, and the client’s WebRTC leg connected normally (we received `session.created` on the data channel). Only the sideband could not find the call.

### Test results on 2026-09-09, 03:20–03:50 UTC (while failing)

| Variant | Extra condition | Sideband result |
| --- | --- | --- |
| A (ephemeral → ephemeral) | — | 404 `call_id_not_found` (~5.5 s) |
| B (ephemeral → API key) | — | 404 `call_id_not_found` (~5.5 s) |
| B | connect only **after** the client peer connection is `connected`; retry every ~6 s for 30 s | 404 every time |
| A | models `gpt-realtime` / `gpt-realtime-mini`; minimal session `{type, model}` | 404 |
| A / B | extra `OpenAI-Beta: realtime=v1` header, or `&model=` in the URL | 404 |
| **C (API key → API key)** | — | **connected; events and function calls work** |

### Same tests on 2026-09-09, 05:36 UTC (about two hours later)

| Variant | Sideband result (4 runs each) |
| --- | --- |
| A (ephemeral → ephemeral) | **connected 4/4** (~0.4 s) |
| B (ephemeral → API key) | 404 `call_id_not_found` 4/4 (~5.5 s) |
| C (API key → API key) | connected |

So in the steady state the sideband connects only with the **same key that created the call** (A and C work, B does not), and for about two hours on Sep 9 even variant A failed. We switched production to variant C, which worked throughout.

## Environment

- Node.js 22, `ws` 8.18.3, built-in `fetch` / `FormData`
- Model `gpt-realtime-1.5`
- Requests from Japan (AWS ap-northeast-1 and a local machine); call IDs are prefixed `rtc_u7_`

## Minimal reproduction (Node 22)

Runs the three variants in order. The SDP is a syntactically valid but non-connecting offer; the sideband result does not depend on media being connected (see the 30-second retry row above).

```js
// node --input-type=module - < repro.mjs (OPENAI_API_KEY must be set)
import WebSocket from 'ws'

const apiKey = process.env.OPENAI_API_KEY
const session = { type: 'realtime', model: 'gpt-realtime-1.5' }
const fp = Array.from({ length: 32 }, (_, i) => ((i * 7) % 256).toString(16).padStart(2, '0').toUpperCase()).join(':')
const sdp = [
  'v=0', 'o=- 4611731400430051336 2 IN IP4 127.0.0.1', 's=-', 't=0 0',
  'a=group:BUNDLE 0', 'a=msid-semantic: WMS',
  'm=audio 9 UDP/TLS/RTP/SAVPF 111', 'c=IN IP4 0.0.0.0', 'a=rtcp:9 IN IP4 0.0.0.0',
  'a=ice-ufrag:abcd', 'a=ice-pwd:abcdefghijklmnopqrstuvwx', 'a=ice-options:trickle',
  `a=fingerprint:sha-256 ${fp}`, 'a=setup:actpass', 'a=mid:0', 'a=recvonly', 'a=rtcp-mux',
  'a=rtpmap:111 opus/48000/2', 'a=fmtp:111 minptime=10;useinbandfec=1', '',
].join('\r\n')

const callIdFrom = r => r.headers.get('Location')?.split('/').pop()

function sideband(callId, key) {
  return new Promise(resolve => {
    const ws = new WebSocket(`wss://api.openai.com/v1/realtime?call_id=${callId}`, {
      headers: { Authorization: `Bearer ${key}` },
    })
    ws.once('open', () => { resolve('OPEN'); ws.close() })
    ws.once('unexpected-response', (_req, res) => {
      let body = ''
      res.on('data', d => (body += d))
      res.on('end', () => resolve(`HTTP ${res.statusCode} ${body}`))
    })
    ws.once('error', e => resolve(`ERROR ${e.message}`))
  })
}

async function createWithEphemeralKey() {
  const secret = await fetch('https://api.openai.com/v1/realtime/client_secrets', {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ session }),
  }).then(r => r.json())
  const res = await fetch('https://api.openai.com/v1/realtime/calls', {
    method: 'POST',
    headers: { Authorization: `Bearer ${secret.value}`, 'Content-Type': 'application/sdp' },
    body: sdp,
  })
  return { callId: callIdFrom(res), ephemeralKey: secret.value, status: res.status }
}

// A) ephemeral-key call, sideband with the same ephemeral key
const a = await createWithEphemeralKey()
console.log('A) ephemeral-key call', a.status, a.callId, '-> sideband(ephemeral):', await sideband(a.callId, a.ephemeralKey))

// B) ephemeral-key call, sideband with the API key (new call: closing a sideband ends the call)
const b = await createWithEphemeralKey()
console.log('B) ephemeral-key call', b.status, b.callId, '-> sideband(API key): ', await sideband(b.callId, apiKey))

// C) unified interface: API key creates the call, API key joins the sideband
const form = new FormData()
form.append('sdp', sdp)
form.append('session', JSON.stringify(session))
const c = await fetch('https://api.openai.com/v1/realtime/calls', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
  body: form,
})
console.log('C) API-key call ', c.status, callIdFrom(c), '-> sideband(API key): ', await sideband(callIdFrom(c), apiKey))
process.exit(0) // keep-alive sockets otherwise hold the process open for a while

```

Output while failing (Sep 9, ~03:30 UTC): A = 404 `call_id_not_found`, B = 404, C = OPEN.  
Output two hours later (Sep 9, 05:36 UTC): A = OPEN, B = 404 `call_id_not_found`, C = OPEN.

## Questions

1. **For this setup — our server relays the SDP and the client never holds a key — is variant C (API key creates the call, API key joins the sideband) the recommended pattern?** We see no reason to keep the ephemeral-key step, but want to confirm before settling on it.
2. Was the sideband 404 for ephemeral-key calls on Sep 9 a temporary instability on the platform side? Nothing appeared on the status page or in the changelog. A SIP report with the same ~5 s delay and the same error body is [here](https://community.openai.com/t/realtime-api-sip-accept-endpoint-takes-5-3s-to-return-404-call-id-not-found/1394315); support said it “does not appear expected”.
3. Variant B is what the guide shows (`Bearer ${EPHEMERAL_KEY}` for `/calls`, `Bearer OPENAI_API_KEY` for the sideband), but it returns 404 for us both while failing and after. If “the sideband must use the same key that created the call” is the intended rule, could the guide say so?

---

<div class="post-metadata">

**Author:** ![akira\_numakura](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/akira_numakura/32/681488_2.png) [@akira\_numakura](https://community.openai.com/u/akira_numakura)\
**Post date:** [September 10, 2026, 12:52am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/2 "2026-09-10T00:52:31Z")

</div>

Update: it is happening again today.

2026-09-10, 00:43 UTC — same tests as in the original post, same account and model:

| Variant | Result (3 runs) |
| --- | --- |
| A (ephemeral key creates the call → ephemeral key on the sideband) | 404 `call_id_not_found` 3/3, ~5.5 s each |
| B (ephemeral key → API key on the sideband) | 404 `call_id_not_found` 2/2 |
| C (API key creates the call → API key on the sideband, unified interface) | connected 3/3, ~0.4 s |

The 404 responses carry no `x-request-id`; `cf-ray` values were `a38a65d9bd43a59e-NRT`, `a38a660129b9e37f-NRT`, `a38a6627d911e357-NRT` (variant A) and `a38a664e58eb7e2f-NRT`, `a38a6675ef623c02-NRT` (variant B).

Two more data points from the same run:

- An ephemeral key still opens a plain Realtime WebSocket session (`wss://api.openai.com/v1/realtime?model=...`) without any problem, so the ephemeral key itself is fine. Only the sideband lookup for a call created with that key fails.
- Retrying the sideband on the same ephemeral-key call a second time (after the first 5.5 s 404) returns the same 404.
- Waiting for the client does not help either: with the browser’s peer connection `connected` and `session.created` received on the data channel (00:49 UTC), the sideband for that ephemeral-key call still returned 404 at 5 s, 12 s, 18 s and 25 s.

Our production, which now uses variant C, is unaffected; the previous version of our code (variant A) fails right now.

---

<div class="post-metadata">

**Author:** ![joinwell52](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/joinwell52/32/756754_2.png) [@joinwell52](https://community.openai.com/u/joinwell52)\
**Post date:** [September 10, 2026, 2:21am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/3 "2026-09-10T02:21:14Z")

</div>

I noticed one other change in C: the request switches from `application/sdp` to multipart with a `session` field. Could that be part of the difference? The repeat runs make C look consistent, but I’d be hesitant to attribute it to the key alone while both variables change.

---

<div class="post-metadata">

**Author:** ![akira\_numakura](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/akira_numakura/32/681488_2.png) [@akira\_numakura](https://community.openai.com/u/akira_numakura)\
**Post date:** [September 10, 2026, 5:28am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/4 "2026-09-10T05:28:06Z")

</div>

@joinwell52 Thanks, that is a fair point — variant C did change two things at once. I separated them: key (ephemeral vs standard API key) × request format (`application/sdp` vs multipart with a `session` field), with the sideband always using the key that created the call.

2026-09-10, 02:25–02:31 UTC, three consecutive rounds, identical every time:

| Call created with | Request format | Sideband result |
| --- | --- | --- |
| ephemeral key | `application/sdp` | 404 `call_id_not_found` |
| ephemeral key | multipart (`sdp` + `session`) | 404 `call_id_not_found` |
| standard API key | `application/sdp` (with `?model=` in the query) | connected |
| standard API key | multipart (`sdp` + `session`) | connected |

All four calls were created successfully (201 with a valid answer SDP); only the sideband differs. So the request format is not the variable — the key that creates the call is. `application/sdp` with a standard API key works fine, so nothing about the old-style request is broken. This also matches the reverse case from the first post: a call created with the standard API key returns 404 on the sideband if the sideband presents an ephemeral key.

For the record, the ephemeral-key path stayed broken for us far longer today than yesterday: we probed every 5 minutes from 00:45 to 04:09 UTC and every single attempt failed, while the API-key path succeeded every time.

On our side we are settling on the API-key (unified interface) approach:

- The official `realtime-server-controlled` example added on 2026-08-27 uses a standard API key for both the call creation and the sideband, for exactly this architecture.
- Looking back at our own code, we originally planned the browser-direct flow where the browser receives an ephemeral token, and we kept minting one after we moved the SDP exchange to the server. That step stopped being necessary at that point and we simply never revisited it, so we are dropping it.

---

<div class="post-metadata">

**Author:** ![mohsinalikhanchanna3](https://avatars.discourse-cdn.com/v4/letter/m/a88e57/32.png) [@mohsinalikhanchanna3](https://community.openai.com/u/mohsinalikhanchanna3)\
**Post date:** [September 10, 2026, 6:18am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/5 "2026-09-10T06:18:00Z")

</div>

Yeah, I think the key detail here is that you separated the two variables. That makes the 404 behavior much easier to understand. If both request formats work with the standard API key but the sideband fails with the ephemeral key, then the key used to create the call seems to be the real difference.

The part about switching to the server side API key flow also makes sense for this setup.

---

<div class="post-metadata">

**Author:** ![joinwell52](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/joinwell52/32/756754_2.png) [@joinwell52](https://community.openai.com/u/joinwell52)\
**Post date:** [September 11, 2026, 2:57am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/6 "2026-09-11T02:57:46Z")

</div>

Thanks for running the four combinations, Akira—that answers my concern about the request format. In those runs, the split follows the credential used to create the call, across both formats. The failing ephemeral case is still worth keeping separate from the working server-side path; switching production avoids it, but doesn’t explain why it changed.

---

<div class="post-metadata">

**Author:** ![tanuj2026](https://avatars.discourse-cdn.com/v4/letter/t/4af34b/32.png) [@tanuj2026](https://community.openai.com/u/tanuj2026)\
**Post date:** [September 11, 2026, 4:46am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/7 "2026-09-11T04:46:31Z")

</div>

Hi @akira_numakura@joinwell52

We started seeing this issue around the same time , starting 10th Sept. The sideband wss connection gets rejected by openai using the ephemeral key, when it was working since the last 4 months. testing with the key chat created the call in the first place, creates a successful connection.  
Is there any official acknowledgement, or confirmation on this issue/sudden change from OpenAI team that you know of?

---

<div class="post-metadata">

**Author:** ![joinwell52](https://sea2.discourse-cdn.com/openai1/user_avatar/community.openai.com/joinwell52/32/756754_2.png) [@joinwell52](https://community.openai.com/u/joinwell52)\
**Post date:** [September 11, 2026, 5:31am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/8 "2026-09-11T05:31:17Z")

</div>

I haven’t found an official acknowledgment of this specific sideband issue yet, tanuj—I checked this thread, the [status history](https://status.openai.com/history), and the [API changelog](https://developers.openai.com/api/docs/changelog). Akira’s tests are the evidence I’ve been following; we haven’t reproduced it ourselves. So I can’t confirm whether the change was intentional or a regression.

---

<div class="post-metadata">

**Author:** ![Takayuki.Kimoto](https://avatars.discourse-cdn.com/v4/letter/t/e47774/32.png) [@Takayuki.Kimoto](https://community.openai.com/u/Takayuki.Kimoto)\
**Post date:** [September 16, 2026, 12:10am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/9 "2026-09-16T00:10:51Z")

</div>

I contacted support because the same issue occurred in our commercial service for customers.  
As a result, they acknowledged it as a bug and fixed it on September 15, 2026, at 06:51 JST (September 14, 2026, at 21:51 UTC).  
Call creation using ephemeral keys and Sideband connections is now working again.

---

<div class="post-metadata">

**Author:** ![Prashant\_Pardesi](https://avatars.discourse-cdn.com/v4/letter/p/f9ae1b/32.png) [@Prashant\_Pardesi](https://community.openai.com/u/Prashant_Pardesi)\
**Post date:** [September 16, 2026, 11:16am UTC](https://community.openai.com/t/ephemeral-keys-trigger-realtime-sideband-404-errors/1396002/10 "2026-09-16T11:16:57Z")

</div>


