A voice note arrives, then a correction. Which request wins?

WhatsApp voice note processing gets awkward when the customer sends a correction before transcription finishes. They ask for two items in the audio, then type “make that three”. If you apply the transcript when it arrives, it can overwrite the newer quantity.

The problem is the order of completion. Downloading audio and transcribing it takes longer than accepting a text message. We need to keep the customer’s request in order even when those jobs finish at different times.

Let’s fix the ordering first. Then we’ll deal with failed transcription and the point where an order can actually be changed.

Reserve a place for the audio before transcribing it

When the audio event arrives, save an inbox row immediately. Give it a local acceptance sequence and a state such as waiting_for_transcript. Don’t wait until you have words before recording that the message exists.

If a text correction arrives next, save it after that slot. A conversation view can now see that earlier input is still unresolved. It shouldn’t treat the correction as an unrelated new order.

Use provider message IDs to recognise repeated deliveries. Keep the original event timestamp as evidence, but remember that local acceptance order doesn’t prove the order of a user’s intent when events arrive late. If ordering matters and the evidence is ambiguous, ask the customer rather than guessing.

Attach the transcript to its original message

A transcript is a result for an existing input slot. It isn’t a new instruction that arrived at transcription completion time.

Store transcript_text, transcript_status, the transcription operation ID and an input version. If you retry the transcription, make sure an older attempt can’t replace a newer accepted result. Keep the source media reference long enough to investigate an error, then follow your retention policy.

This also makes correction handling simpler. Read the original audio instruction first, then apply the customer’s later text. The interpretation step can see that “make that three” refers to a quantity already mentioned.

Build an ordered local example

The example below deliberately completes the earlier audio slot after the correction slot. A reducer rebuilds the current proposal from the ordered inputs. It waits while a required slot is unresolved.

import assert from 'node:assert/strict';
const slots = [
  { sequence: 1, state: 'waiting', command: null },
  { sequence: 2, state: 'ready', command: { quantity: 3 } },
];
function proposal(input) {
  if (input.some(s => s.state === 'failed')) return { state: 'clarify' };
  if (input.some(s => s.state === 'waiting')) return { state: 'waiting' };
  let quantity = null;
  for (const slot of [...input].sort((a,b) => a.sequence-b.sequence)) {
    if (slot.state === 'superseded') continue;
    if (slot.command?.quantity !== undefined) quantity = slot.command.quantity;
  }
  return { state: 'ready', quantity };
}
assert.equal(proposal(slots).state, 'waiting');
slots[0] = { sequence: 1, state: 'ready', command: { quantity: 2 } };
assert.deepEqual(proposal(slots), { state: 'ready', quantity: 3 });
slots[0].state = 'failed';
assert.equal(proposal(slots).state, 'clarify');
slots[0].state = 'superseded';
assert.equal(proposal(slots).quantity, 3);
console.log('PASS: late transcript preserves correction; failure asks for clarity');

The final quantity is three, although the transcript completed last. That’s the rule we wanted to preserve. It doesn’t claim to understand arbitrary speech: the small command format stands in for your interpretation layer.

We also check a failed transcription. The application must not silently drop the audio and pretend that the remaining correction is enough. It produces a clarification state instead.

Keep downloading, transcription and interpretation separate

A practical job chain starts with accepted media, then download, then transcription, then conversation interpretation. Each stage should record its output against the original inbox ID.

Don’t pass large media bytes through every queue message. Pass an authorised reference and verify that it belongs to the business and customer being processed. If media retrieval is temporary, record the failure and follow the provider’s current retrieval rules rather than assuming an old URL will remain usable.

Set a deadline for each stage. Node’s abort signals can stop cooperating local work, but cancellation doesn’t guarantee that an external transcription service stopped processing or charging. Track the external operation when the service provides one. Node AbortController documentation.

If the customer says “ignore that voice note”, mark the slot superseded. A late transcript can still be stored for that operation, but it must not re-enter the active proposal.

Store the proposal separately from the order

Use a conversation proposal with a revision, input IDs and interpreted fields. Keep a confirmed order in a different record. That distinction matters when the customer corrects themselves after payment or fulfilment starts.

Before checkout, rebuild or validate the proposal against all relevant resolved inputs. Check the proposal revision again before creating the order. If anything has changed, ask for confirmation on the updated quantity and amount.

Once an order is committed, a correction becomes a change request. Don’t mutate the order merely because a later transcript mentions a different address. The order service should decide what can still be changed and record the outcome.

Handle uncertainty in ordinary language

Audio may be unclear. The customer may say “three” while the transcript says “free”. Preserve uncertainty from transcription and interpretation instead of converting every field into a confident value.

Ask one specific question: “Did you mean three pairs?” That is easier to answer than sending a long explanation of your pipeline. The customer only needs enough information to finish the request.

If transcription is taking longer than expected, a short progress update can explain the delay. Keep it tied to a real processing state. Don’t announce that an order is ready while an earlier input is unresolved.

Test results arriving in the wrong order

Test audio followed by text, text followed by audio, failed audio, duplicate completion and a cancelled slot completing late. Then use two workers to complete different stages concurrently and confirm that older results cannot overwrite accepted newer state.

Measure time spent downloading, waiting for transcription and waiting for clarification separately. Otherwise, a slow conversation can look like slow inference even when the real issue is a missing media result.

Start by reserving an inbox slot before transcription. That one change gives the rest of your system a stable place to attach results and a clear way to know when it isn’t ready to answer.

Run the example and adapt it

Save the code as 03-voice-note-corrections.mjs and run it with Node.js 24 or later:

node 03-voice-note-corrections.mjs

The code above includes assertions you can run locally. It tests the local decision logic with controlled inputs; it doesn’t connect to a live provider or establish production performance. Keep that boundary when adapting it to your application.

Technical references