LeadRefinery — How the Platform Works

A call from start to end

Traced at commit ffe4e48 · libs/temporal/src/workflows/call-attempt.ts · apps/api/src/app/webhooks/{retell,telnyx}.controller.ts · libs/temporal/src/activities/call-attempt.ts

Summary

  1. A workflow owns one call attempt.
  2. The workflow moves through six states: created, dialing, connected, ended, reconciling, disposed.
  3. Two providers send messages while the call happens.
  4. The platform refuses a message with an invalid signature. The platform answers 401.
  5. The system writes each accepted message once. The platform discards a repeat delivery.
  6. A message does not change the database directly. The message becomes a signal to the workflow.
  7. The workflow holds the state. The order of arrival therefore does not matter.
  8. If the analysis never arrives, a 5-minute timeout disposes the call as needs_review.
A workflow moves through six states: created, dialing, connected, ended, reconciling and disposed. Two providers send messages while this happens. Each message is checked for a valid signature and refused with 401 if the signature is wrong. Each accepted message is written once using a de-duplication key made from the provider, the call and the event type, so a repeated delivery is absorbed. Messages are sent into the workflow as signals. If the analysis message never arrives, a five-minute timeout moves the call to a needs-review outcome instead of leaving it open.
Workflow 1 of 2 — the call. The workflow owns the state. Messages arrive as signals, not as database writes.

Workflow 1 — the call

The sequence

  1. The safety gate allows the dial. See The dial gate.
  2. The workflow starts in the state created.
  3. The system registers the call with the voice provider and places the call with the carrier.
  4. The state becomes dialing.
  5. The carrier sends call.initiated.
  6. A person answers. The carrier sends call.answered and call.bridged.
  7. The state becomes connected.
  8. The call ends. The carrier sends call.hangup. The voice provider sends call_ended.
  9. The state becomes ended.
  10. The state becomes reconciling. The workflow waits for the analysis.
  11. The voice provider sends call_analyzed with the transcript.
  12. The system resolves the outcome. See Outcome resolution.
  13. The state becomes disposed. The attempt now has one written outcome.

Workflow 2 — one arriving message

A provider sends a message. The platform checks the signature before it reads the body and answers 401 for a wrong or missing signature, recording which of the two it was. It then builds a de-duplication key from the provider, the call id and the event type, and writes the event only if that key is new. A repeat delivery writes nothing and the platform still answers success, so the provider can retry freely. Finally the platform sends the event into the workflow as a signal. The workflow holds the state, so a message arriving out of order does no harm.
Workflow 2 of 2 — ingest. Four steps. The first two can refuse; the third makes a repeat harmless.

The four steps

  1. The system verifies the signature before it reads the body.
  2. An invalid signature gives 401. A missing signature also gives 401.
  3. The system records the refusal with the outcome invalid_signature or missing_signature.
  4. The system builds a de-duplication key from the provider, the call id and the event type.
  5. The system writes the event with onConflictDoNothing against that key.
  6. A second delivery of the same message writes nothing.
  7. The provider can therefore retry as often as it needs to.
  8. The system sends the event to the workflow as a signal.

Why this shape. The workflow holds the state, not the message. A duplicate message does no harm, because the database discards the second write. A message that arrives out of order does no harm, because the workflow decides what each one means.

The reconcile timeout

  1. The default timeout is 5 minutes.
  2. The timeout starts when the state becomes reconciling.
  3. If the analysis does not arrive, the workflow disposes the attempt as needs_review.
  4. A person then reviews the attempt.
  5. This is the only path that produces an outcome without the provider.
  6. Every other outcome comes from messages that arrived.
  7. A call is never left open.

Outcome resolution — what the platform does with the transcript once the call has ended.