> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.telekesher.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.telekesher.dev/_mcp/server.

# Awaiting asynchronous operations

## The model

A `202 Accepted` [Operation](/operation) means authentication, authorization,
schema, and basic validation passed and 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/start` UUID, including when a later stop command
caused the event.

Generate that UUID yourself, install a listener, and only then send the command.
This gives application code a normal `await`:

```javascript
const stopped = await runOperation(
  operationUuid => startPlayback(sessionUuid, {
    operation_uuid: operationUuid,
    type: 'files',
    urls: ['https://example.com/welcome.mp3'],
  }),
  {
    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 runOperation(
  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('Operation timed out')),
      timeoutMs,
    );

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

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

Call `handleOperationEvent` 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.
* `operation_uuid` correlates events. Use `Idempotency-Key` separately to make
  supported command retries safe.
* This in-memory example assumes one process. Multi-instance applications need
  shared event routing.

Planned official SDKs will provide this clean `await` API without requiring you
to implement correlation, listeners, timeouts, or typed results yourself.