> ## 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.

# Callbacks

> Learn about Callback contract payload shape, and JavaScript vs React usage for onSubmit, onCancel, and onDraft in the Dictation SDK

<div className="quick-summary-wrapper">
  <div className="quick-summary-header">
    <span className="quick-summary-icon" aria-hidden="true" />

    <span className="quick-summary-title">Quick summary</span>
  </div>

  <div className="quick-summary-content">
    The Suki Dictation SDK supports three callbacks: `onSubmit`, `onCancel`, and `onDraft`. These callbacks are called when the user commits, cancels, or drafts the dictation respectively. You can use these callbacks to handle the dictation result, cancel the session, or receive intermediate updates while the user is dictating.
  </div>

  <div className="quick-summary-footer">
    <span className="quick-summary-footer-icon" aria-hidden="true" />

    <span className="quick-summary-footer-text">Last updated:</span>
    <span className="quick-summary-footer-date">June 2026</span>
  </div>
</div>

**Callbacks** allow your application to run your own code when the user commits, cancels, or moves on without committing during dictation. You supply functions; the Suki Dictation SDK calls them when those events occur. You do **not** poll on a timer or in a loop for new text.

**Registering callbacks**

When you start a session, register them in **JavaScript** as properties on the object you pass to `DictationClient.show()`, or in **React** as props on `<Dictation>`.

**Callback execution**

It calls **`onSubmit`** after the user commits, and **`onCancel`** if they leave without committing. If you supply **`onDraft`**, the SDK may call it when the user does **not** submit and switches to another field (or otherwise moves on without committing). **`onDraft`** is **not** invoked on every live transcript change while dictation stays on one field. The SDK does **not** persist draft text to your systems; only your **`onDraft`** handler decides whether to store or send it to your partner backend.

**Payload**

Every callback receives the same argument: one object, `{ fieldId, text }`. The `fieldId` value is whatever you passed into `show()` or `<Dictation>`. A common pattern is to set it to the **same value as the target control's `id`**, so `onSubmit` can use `document.getElementById(fieldId)` and write `text` into that element.

## Supported callbacks

Dictation SDK supports the following callbacks:

<Expandable title="Supported callbacks" defaultOpen="true">
  <ResponseField name="onSubmit" type="function" required>
    Runs when the user commits the dictation result.
  </ResponseField>

  <ResponseField name="onCancel" type="function">
    **Optional**: Runs when the user discards the session without committing.
  </ResponseField>

  <ResponseField name="onDraft" type="function">
    **Optional**. May run when the user leaves dictation **without** submitting and switches context (for example, to another field). Use it if you want to capture unstaged transcript text; the SDK does not save or forward it unless you do.
  </ResponseField>
</Expandable>

**Mode** controls how dictation is shown. Set it to **`in-field`** when the UI is tied to a container on your page, or **`scratchpad`** for a standalone surface. Refer to [In-field mode](/dictation-sdk/guides/in-field-mode) and [Scratchpad mode](/dictation-sdk/guides/scratchpad-mode) guides for behavior and layout.

<Tip>
  For every other option you pass to `DictationClient.show()` or to `<Dictation>` as props, including **`rootElement`**, **`initialText`**, and these callbacks, use the [Configuration](/dictation-sdk/guides/configuration) reference.
</Tip>

## Callback contract payload shape

All callbacks receive a **single** argument: an object with these fields:

<Expandable title="Callback contract payload shape" defaultOpen="true">
  <ResponseField name="fieldId" type="string" required>
    This is the string you invent to name this dictation session. The SDK includes it in every callback so you know which field the `text` is for. Using the same text as your input’s `id` (for example `clinical-notes`) is a simple way to tie them together.
  </ResponseField>

  <ResponseField name="text" type="string" required>
    The transcript string for this callback (committed result, state at cancel, or interim draft, depending on which handler ran).
  </ResponseField>

  **Example:**

  ```javascript JavaScript theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  // After the user commits, your onSubmit might receive:
  {
    fieldId: "clinical-notes",
    text: "The user said hello.",
  }
  ```
</Expandable>

<Tip>
  For structured notes, use **stable**, **unique** ids per section, for example **`soap.subjective`**, **`soap.objective`**, **`soap.assessment`**, **`soap.plan`**, or **`scratchpad`** for a floating session. See [Field IDs](/dictation-sdk/guides/configuration#field-ids-in-practice).
</Tip>

<Warning>
  If **`onSubmit`** is missing, dictation often **closes immediately** after the user acts. Always implement `onSubmit`.
</Warning>

## Code examples

<View title="JavaScript" icon="js">
  Use callbacks as properties on the object you pass to **`await dictationClient.show({ ... })`**. They sit alongside `mode`, `fieldId`, `rootElement`, `initialText`, and other options from [Configuration](/dictation-sdk/guides/configuration).

  The iframe **resizes to the container** you pass as `rootElement` when you use `in-field` mode.

  ```javascript JavaScript  theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  import { SukiAuthManager } from "@suki-sdk/core";
  import { DictationClient } from "@suki-sdk/dictation";

  const authManager = new SukiAuthManager({
    partnerId: "YOUR_PARTNER_ID", // Required
    partnerToken: "YOUR_PARTNER_TOKEN", // Required
    environment: "staging", // Optional
    loginOnInitialize: true,
    autoRegister: false, // Optional - default is false
    providerId: "YOUR_PROVIDER_ID", // Optional - required if autoRegister is true
    providerName: "YOUR_PROVIDER_NAME", // Optional - required if autoRegister is true
    providerOrgId: "YOUR_PROVIDER_ORG_ID", // Optional - required if autoRegister is true
    providerSpecialty: "YOUR_PROVIDER_SPECIALTY", // Optional - required if autoRegister is true
  });

  const dictationClient = new DictationClient({ authManager });

  // Example markup for in-field:
  // <div id="clinical-notes-dictation-root"></div>
  // <textarea id="clinical-notes"></textarea>

  await dictationClient.show({
    mode: "in-field",
    fieldId: "clinical-notes", // same string as id="clinical-notes" on your textarea
    rootElement: document.getElementById("clinical-notes-dictation-root"),
    initialText: document.getElementById("clinical-notes")?.value ?? "",
    onSubmit: ({ fieldId, text }) => {
      const el = document.getElementById(fieldId);
      if (el) el.value = text;
    },
    onCancel: ({ fieldId, text }) => {
      // Optional: user cancelled without committing
    },
    onDraft: ({ fieldId, text }) => {
      // Optional: e.g. user switched fields without submit; persist draft in your app if needed
    },
  });
  ```

  <Note>
    Calling **`show()`** again **replaces** the active session; you do not need to call **`hide()`** between fields for that pattern. Wrap `show()` in `try` / `catch` if you want to log configuration or auth failures.

    Refer to the [Error handling](/dictation-sdk/guides/error-handling) guide for more details.
  </Note>

  <Tip>
    For more on imperative `show()` and one client per page scope, refer to the [JavaScript integration](/dictation-sdk/javaScript-integration/javaScript) guide for more details.
  </Tip>
</View>

<View title="React" icon="react">
  Pass the same three handlers as **props** on **`<Dictation>`**. Build **`DictationClient`** once (with `SukiAuthManager`), wrap your tree with **`<DictationProvider client={client}>`**, then render `Dictation` where dictation should be active.

  When `Dictation` **unmounts**, the SDK calls **`hide()`** for you.

  ```jsx React  theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
  import { useMemo, useState } from "react";
  import { SukiAuthManager } from "@suki-sdk/core";
  import { DictationClient } from "@suki-sdk/dictation";
  import { DictationProvider, Dictation } from "@suki-sdk/dictation-react";

  function NotesApp() {
    const client = useMemo(() => {
      const authManager = new SukiAuthManager({
        partnerId: "YOUR_PARTNER_ID", // Required
        partnerToken: "YOUR_PARTNER_TOKEN", // Required
        environment: "staging", // Optional
        loginOnInitialize: true,
        autoRegister: false, // Optional - default is false
        providerId: "YOUR_PROVIDER_ID", // Optional - required if autoRegister is true
        providerName: "YOUR_PROVIDER_NAME", // Optional - required if autoRegister is true
        providerOrgId: "YOUR_PROVIDER_ORG_ID", // Optional - required if autoRegister is true
        providerSpecialty: "YOUR_PROVIDER_SPECIALTY", // Optional - required if autoRegister is true
      });
      return new DictationClient({ authManager });
    }, []);

    const [notes, setNotes] = useState("");
    // Drive from your UI: e.g. setActiveField("clinical-notes") when this field owns dictation.
    const [activeField, setActiveField] = useState(null);

    const handleSubmit = ({ fieldId, text }) => {
      // fieldId matches id="clinical-notes" below if you use that pattern
      setNotes(text);
    };

    const handleCancel = ({ fieldId, text }) => {
      // Optional
    };

    const handleDraft = ({ fieldId, text }) => {
      // Optional: e.g. save unstaged text when user leaves without submit
    };

    return (
      <DictationProvider client={client}>
        <textarea
          id="clinical-notes"
          value={notes}
          onChange={(e) => setNotes(e.target.value)}
        />
        {activeField === "clinical-notes" && (
          <Dictation
            fieldId="clinical-notes"
            mode="in-field"
            initialText={notes}
            onSubmit={handleSubmit}
            onCancel={handleCancel}
            onDraft={handleDraft}
          />
        )}
      </DictationProvider>
    );
  }
  ```

  <Note>
    If you already create `client` higher in the tree, pass it into `DictationProvider` the same way. Use state such as **`activeField`** so only **one** field owns the session.

    Refer to [React integration](/dictation-sdk/react-integration/react) guide for more details.
  </Note>
</View>

## Next steps

<Icon icon="file-lines" iconType="solid" /> Refer to [Dictation modes](/dictation-sdk/guides/in-field-mode) and [Scratchpad mode](/dictation-sdk/guides/scratchpad-mode) guides to learn more about the different modes and how to use them.
