> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send START_TIME Before Ambient Audio

> Send one START_TIME frame on /ws/stream before the first PCM chunk for Ambient and Form filling

export const CbRecipeMeta = ({items = []}) => <div className="cb-recipe-meta">
    {items.map(item => <span className={`cb-recipe-pill cb-recipe-pill--${item.type}${item.tone ? ` cb-recipe-pill--${item.tone}` : ""}`} key={`${item.type}-${item.label}`}>
        {item.type === "time" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <circle cx="12" cy="12" r="10" />
            <polyline points="12 6 12 12 16 14" />
          </svg> : item.type === "level" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M2 20h.01" />
            <path d="M7 20v-4" />
            <path d="M12 20v-8" />
            <path d="M17 20V8" />
            <path d="M22 20V4" />
          </svg> : item.type === "surface" ? <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <polyline points="16 18 22 12 16 6" />
            <polyline points="8 6 2 12 8 18" />
          </svg> : <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M12 3v3" />
            <path d="M12 18v3" />
            <path d="M3 12h3" />
            <path d="M18 12h3" />
            <path d="M5.6 5.6l2.1 2.1" />
            <path d="M16.3 16.3l2.1 2.1" />
            <path d="M5.6 18.4l2.1-2.1" />
            <path d="M16.3 7.7l2.1-2.1" />
          </svg>}
        {item.label}
      </span>)}
  </div>;

export const CbRecipePage = ({title, description, meta, recipeId, sidebar = {}, children}) => {
  const {tutorialHref, tutorialLabel = "Open Full Tutorial", apiHref, apiLabel = "View API Reference", authSections = [], glance = [], headers = []} = sidebar;
  return <div className="hp-wrap api-overview-wrap docs-frame-wrap cb-recipe-wrap" data-cb-recipe data-disable-read-time="true">
      <header className="cb-recipe-hero tut-hub-intro">
        <h1 className="sdk-overview-main-title api-overview-hero-page-title">{title}</h1>
        {description ? <div className="wse-prose">{description}</div> : null}
        {meta ? <div className="cb-recipe-meta-slot">{meta}</div> : null}
      </header>

      <div className="cb-recipe-body">
        <div className="cb-recipe-layout">
          <div className="cb-recipe-main prose prose-gray dark:prose-invert">{children}</div>

          <aside className="cb-recipe-sidebar" aria-label="Recipe actions and metadata">
            {recipeId ? <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--run">
                <div className="cb-run" data-cb-run data-recipe-id={recipeId} data-recipe-title={title}>
                  <button type="button" className="cb-run-trigger" aria-expanded="false">
                    <svg className="cb-run-trigger-icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
                      <path d="M8 5v14l11-7L8 5z" />
                    </svg>
                    <span>Run This Recipe</span>
                    <svg className="cb-run-trigger-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <polyline points="6 9 12 15 18 9" />
                    </svg>
                  </button>
                </div>
              </div> : null}

            <div className="cb-recipe-sidebar-card cb-recipe-sidebar-card--actions">
              <div className="cb-recipe-sidebar-actions">
                {tutorialHref ? <a className="cb-recipe-action cb-recipe-action--primary" href={tutorialHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
                      <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
                    </svg>
                    <span>{tutorialLabel}</span>
                  </a> : null}
                {apiHref ? <a className="cb-recipe-action cb-recipe-action--secondary" href={apiHref}>
                    <svg className="cb-recipe-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
                      <path d="M8 4H6a2 2 0 0 0-2 2v4" />
                      <path d="M8 20H6a2 2 0 0 1-2-2v-4" />
                      <path d="M16 4h2a2 2 0 0 1 2 2v4" />
                      <path d="M16 20h2a2 2 0 0 0 2-2v-4" />
                    </svg>
                    <span>{apiLabel}</span>
                  </a> : null}
              </div>
              <div className="cb-recipe-sidebar-caption">
                Step-by-step tutorial and API reference for this workflow.
              </div>
            </div>

            {authSections.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Auth</div>
                {authSections.map(section => <div className="cb-recipe-auth-block" key={section.title}>
                    <div className="cb-recipe-auth-title">{section.title}</div>
                    <div className="cb-recipe-sidebar-copy">
                      {section.text}{" "}
                      {section.href ? <a href={section.href}>{section.linkLabel || "Learn more"}</a> : null}
                    </div>
                    {section.scopes?.length ? <div className="cb-recipe-scope-pills cb-recipe-auth-scopes">
                        {section.scopes.map(scope => <span className="cb-recipe-scope-pill" key={scope}>
                            {scope}
                          </span>)}
                      </div> : null}
                  </div>)}
              </div> : null}

            {glance.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">At a glance</div>
                <dl className="cb-recipe-glance">
                  {glance.map(row => <div className="cb-recipe-glance-row" key={row.label}>
                      <dt>{row.label}</dt>
                      <dd>{row.value}</dd>
                    </div>)}
                </dl>
              </div> : null}

            {headers.length ? <div className="cb-recipe-sidebar-card">
                <div className="cb-recipe-sidebar-kicker">Required headers</div>
                <div className="cb-recipe-auth-title">Partner API</div>
                <div className="cb-recipe-scope-pills">
                  {headers.map(h => <span className="cb-recipe-scope-pill" key={h}>
                      {h}
                    </span>)}
                </div>
              </div> : null}
          </aside>
        </div>
      </div>
    </div>;
};

<CbRecipePage
  recipeId="send-start-time-before-ambient-audio"
  title="Send START_TIME Before Ambient Audio"
  description="On `/ws/stream`, send one `START_TIME` frame before the first PCM chunk. Skipping it breaks the Ambient and Form filling stream contract. Dictation on `/ws/transcribe` does not use `START_TIME`."
  meta={
<CbRecipeMeta
  items={[
    { type: "time", label: "5 min" },
    { type: "level", label: "Beginner" },
    { type: "product", label: "Ambient" },
    { type: "surface", label: "API", tone: "api" },
  ]}
/>
}
  sidebar={{
tutorialHref: "/documentation/tutorials/ambient-websocket-code-example",
apiHref: "/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient",
apiLabel: "View Wire Format",
authSections: [
  {
    title: "Partner API",
    text: "Authenticate the WebSocket first. Browser clients use Sec-WebSocket-Protocol. See",
    href: "/documentation/cookbooks/browser-websocket-auth",
    linkLabel: "Authenticate Browser WebSockets",
    scopes: ["sdp_suki_token", "ambient_session_id"],
  },
],
glance: [
  { label: "Product", value: "Ambient" },
  { label: "Surface", value: "Partner API" },
  { label: "Time", value: "~5 min" },
  { label: "Level", value: "Beginner" },
],
}}
>
  ## Problem

  You open `/ws/stream` and send PCM right away. The stream misbehaves or the session never produces usable content.

  Ambient and Form filling require one `START_TIME` message per stream segment before any `AUDIO` frames. Dictation on `/ws/transcribe` does not use `START_TIME`.

  ## Architecture

  ```mermaid actions={false} theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  %%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#FFFBDE','primaryTextColor':'#1C1C1C','primaryBorderColor':'#FFD147','lineColor':'#6F5410','secondaryColor':'#FFFDF5','tertiaryColor':'#FFEB94','fontSize':'13px'}}}%%
  flowchart LR
    A[WebSocket open] --> B[START_TIME]
    B --> C[AUDIO PCM chunks]
    C --> D[RU9G]
  ```

  ## Prerequisites

  <Check>Completed [Partner onboarding](/documentation/get-started/partner-onboarding) and have a valid `sdp_suki_token`.</Check>

  <Check>An open WebSocket on `wss://sdp.suki.ai/ws/stream` for an Ambient or Form filling session.</Check>

  <Check>You are not using Dictation `/ws/transcribe` for this recipe. Dictation skips `START_TIME`.</Check>

  ## Solution

  <Columns cols={3}>
    <Card title="Before">
      Create the session and open `/ws/stream` ([Authenticate Browser WebSockets](/documentation/cookbooks/browser-websocket-auth) if you are in a browser).
    </Card>

    <Card title="This Recipe">
      Send one `START_TIME` frame before the first PCM chunk.
    </Card>

    <Card title="Next">
      Stream PCM, then [End Ambient after Streaming](/documentation/cookbooks/end-ambient-after-streaming).
    </Card>
  </Columns>

  Send one JSON text frame:

  ```json theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  { "type": "START_TIME", "data": "<base64 of RFC 3339 timestamp>" }
  ```

  `data` is standard Base64 of the UTF-8 bytes of an RFC 3339 timestamp, for example `2026-04-25T12:34:56Z`. See [Ambient streaming wire format](/documentation/how-to/audio-streaming/websocket-streaming-wire-format-ambient).

  <Tabs>
    <Tab title="TypeScript">
      ```typescript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
      // send-start-time-before-ambient-audio.ts
      // Call once after the ambient (or Form filling) WebSocket opens, before PCM.

      function toBase64Utf8(text: string): string {
        if (typeof Buffer !== "undefined") {
          return Buffer.from(text, "utf8").toString("base64");
        }
        return btoa(text);
      }

      /** RFC 3339 UTC without milliseconds, for example 2026-04-25T12:34:56Z */
      function rfc3339Now(): string {
        return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
      }

      function sendStartTime(ws: WebSocket): void {
        const stamp = rfc3339Now();
        ws.send(
          JSON.stringify({
            type: "START_TIME",
            data: toBase64Utf8(stamp),
          })
        );
      }

      // Example after open:
      // sendStartTime(ws);
      // then send AUDIO frames with base64 PCM in the data field
      ```
    </Tab>

    <Tab title="Python">
      ```python theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
      # send_start_time_before_ambient_audio.py
      # Call once after the ambient (or Form filling) WebSocket opens, before PCM.

      import base64
      import json
      from datetime import datetime, timezone


      def rfc3339_now() -> str:
          # Example shape: 2026-04-25T12:34:56Z
          return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")


      def send_start_time(ws) -> None:
          stamp = rfc3339_now()
          ws.send(
              json.dumps(
                  {
                      "type": "START_TIME",
                      "data": base64.b64encode(stamp.encode("utf-8")).decode("ascii"),
                  }
              )
          )


      # Example after open:
      # send_start_time(ws)
      # then send AUDIO frames with base64 PCM in the data field
      ```
    </Tab>
  </Tabs>

  <Steps>
    <Step title="Open the ambient stream socket">
      Connect to `wss://sdp.suki.ai/ws/stream` with session auth. Form filling uses the same path and wire format.
    </Step>

    <Step title="Send START_TIME once">
      Encode an RFC 3339 timestamp as UTF-8, Base64 it, and send `{ "type": "START_TIME", "data": "..." }` before any PCM.
    </Step>

    <Step title="Stream AUDIO frames">
      Send `{ "type": "AUDIO", "data": "<base64 pcm>" }` in about 100 ms chunks. Do not send binary WebSocket frames.
    </Step>

    <Step title="End the segment">
      Finish with `RU9G`, close the socket, and call REST end. See [End Ambient after Streaming](/documentation/cookbooks/end-ambient-after-streaming).
    </Step>
  </Steps>

  ## Try it

  <Card>
    * **START\_TIME is first**

      After open, the first outbound message on `/ws/stream` should be `START_TIME`, then PCM `AUDIO` frames.

    * **Form filling uses the same marker**

      Form filling streams on `/ws/stream` with the same `START_TIME` + `data` PCM + `RU9G` contract.

    * **Dictation does not**

      On `/ws/transcribe`, skip `START_TIME` and end with `AUDIO_END` instead of `RU9G`.
  </Card>

  ## Common mistakes

  <Warning>
    * Send PCM before `START_TIME` on `/ws/stream`.
    * Put the raw timestamp string in `data` without Base64.
    * Send `START_TIME` on Dictation `/ws/transcribe`.
  </Warning>

  ## Related cookbooks

  <Columns cols={2}>
    <Card title="Authenticate Browser WebSockets" href="/documentation/cookbooks/browser-websocket-auth" arrow={true} icon="book-open">
      Auth browser WebSocket with protocols.
    </Card>

    <Card title="End Ambient after Streaming" href="/documentation/cookbooks/end-ambient-after-streaming" arrow={true} icon="book-open">
      Send RU9G, then end session.
    </Card>
  </Columns>
</CbRecipePage>
