> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, members, and room playback /api/rooms; Session playback /api/playback; Recording /api/recording; DTMF /api/keypad-input; Messages /api/messages; WebSocket Session /api/web-socket. Use /api/event-delivery for transport behavior. Endpoint pages name relevant events, but these overviews are the canonical references.

# Handling asynchronous commands

## How accepted commands resolve

A `202 Accepted` response confirms that authentication, authorization, schema,
and basic validation passed. It means the command entered the processing queue.
A failure before that point is a synchronous HTTP error.

Processing may then emit a success event, a failure event, or multiple lifecycle events.
They normally share the command's `operation_uuid`. `recording.ended` carries
the originating Recording create operation UUID, even if a later stop command triggered
it.

`operation_uuid` is an opaque correlation token, not a separate resource. There
is no Operation endpoint to fetch or poll. Generate the UUID, install a listener,
and only then send the UUID in the optional `Operation-Id` request header:

```javascript
const stopped = await waitForCommand(
  operationUuid => post(
    `/v1/sessions/${sessionUuid}/playback/play`,
    {
      type: 'files',
      urls: ['https://example.com/welcome.mp3'],
    },
    { headers: { 'Operation-Id': operationUuid } },
  ),
  {
    resolveOn: ['playback.stopped'],
    rejectOn: ['playback.failed'],
  },
);
```

## A small Node.js wrapper

```javascript
const crypto = require('node:crypto');
const { EventEmitter } = require('node:events');

const events = new EventEmitter();

function waitForCommand(
  sendCommand,
  { resolveOn, rejectOn, timeoutMs = 30_000 },
) {
  const operationUuid = crypto.randomUUID();

  return new Promise((resolve, reject) => {
    const finish = (callback, value) => {
      clearTimeout(timeout);
      events.off(operationUuid, onEvent);
      callback(value);
    };

    const onEvent = event => {
      if (resolveOn.includes(event.event)) finish(resolve, event);
      if (rejectOn.includes(event.event)) finish(reject, event);
    };

    events.on(operationUuid, onEvent);

    const timeout = setTimeout(
      () => finish(reject, new Error('Command outcome timed out')),
      timeoutMs,
    );

    void (async () => {
      try {
        await sendCommand(operationUuid);
      } catch (error) {
        finish(reject, error);
      }
    })();
  });
}

function handleCommandEvent(event) {
  if (event.operation_uuid) {
    events.emit(event.operation_uuid, event);
  }
}
```

Call `handleCommandEvent` only after verifying and deduplicating the webhook.
The listener is registered before dispatch, so a fast event cannot be missed.
Non-terminal events are ignored until an event listed in `resolveOn` or
`rejectOn` arrives.

## Keep in mind

* Choose terminal events from the command's API reference and the relevant
  webhook reference. Not every command has one, and a local timeout does not
  cancel remote processing.
* Send caller-supplied correlation in `Operation-Id`; responses and webhooks use
  `operation_uuid`. Use `Idempotency-Key` separately to make supported command
  retries safe.
* This in-memory example assumes one process. Multi-instance applications need
  shared event routing.

The API keeps `operation_uuid` as correlation metadata without modeling an
Operation resource. Command outcomes belong to the affected Session, Playback,
Recording, or Room lifecycle.