DEV Community

tony chen
tony chen

Posted on

Webhook Acceptance Tests for Batch Audio API Jobs: Support Calls and Podcasts

A two-hour recording turns a convenient speech endpoint into a distributed job system. Short answer: choose a batch audio transcription API by proving that async jobs and webhook recovery preserve one durable result for every support call or podcast, even when submission or delivery repeats; transcript quality comes next, measured on your own recordings.

The simple approach is submit, wait for a callback, then paste the transcript into a summarization prompt. It fails as an evaluation design because a clean demo answers none of the awkward questions: Was the upload accepted before the client timed out? Can the same callback arrive twice? What happens when completion arrives before an earlier progress event? Does a retry create another billable job? A notebook can hide all of that. Production can't.

My selection unit would therefore be the entire run, not a single API response: media reference, local request ID, remote job ID, state transitions, transcript artifact, evaluation result, and downstream prompt version. This is an experiment note, not a vendor ranking. The winner is the implementation that passes the workload's acceptance suite with the least application-specific recovery logic.

What should an async API guarantee for long audio transcription jobs?

Start with a state machine you own. A useful minimum is prepared -> submitted -> running -> succeeded | failed, plus a separately recorded delivery status. Don't let a webhook directly overwrite an application row called transcript; validate the event, append it to an inbox, and let a worker reconcile the job. This makes callback delivery an input to your system rather than the source of truth.

The distinction matters because HTTP method semantics don't automatically make job creation safe to repeat. RFC 9110 defines PUT, DELETE, and safe methods as idempotent, while POST is not idempotent by definition. It also cautions clients against automatically retrying a non-idempotent request unless they know the request was not applied or can detect that the original request was never applied. An API can offer its own deduplication contract, but that contract needs to be explicit and testable.

No magic here.

Before adopting any service, I would require written answers to a compact contract: how clients supply a stable request identifier; how long deduplication lasts; whether polling and callbacks expose the same terminal artifact; which event identifier is stable across delivery attempts; whether events carry a creation time and job revision; and how expired media or results are represented. If any answer is absent, the consumer must assume duplicates and reorderings are possible. I'm not sure a generic timeout value can be defensible across hour-long calls and multi-speaker podcasts; queue delay and media duration vary, so production observations should determine that threshold.

There is a real limitation to this architecture. A durable inbox, reconciler, and poller add storage and operational work. For a user waiting on a thirty-second voice note, a synchronous request may be the clearer choice. Stick with synchronous processing when the provider's documented request limit, your latency budget, and your retry model all fit comfortably inside one interaction. Use asynchronous jobs when recordings outlive request timeouts or must survive deploys and worker restarts.

Build an acceptance ledger before comparing transcripts

The focused example below models the part most teams skip. It deliberately has no vendor endpoint: transport adapters translate a provider's payload into Event, while the ledger enforces invariants. A repeated event becomes a no-op, an older revision can't roll a job backward, and only a terminal success attaches an artifact. Database constraints should enforce the same uniqueness rules in production; this in-memory version makes the behavior easy to scan in a notebook and easy to port into an eval harness.

from dataclasses import dataclass, field
from enum import Enum


class State(str, Enum):
    SUBMITTED = "submitted"
    RUNNING = "running"
    SUCCEEDED = "succeeded"
    FAILED = "failed"


@dataclass(frozen=True)
class Event:
    event_id: str
    local_request_id: str
    remote_job_id: str
    revision: int
    state: State
    artifact_uri: str | None = None


@dataclass
class Job:
    remote_job_id: str
    revision: int = -1
    state: State = State.SUBMITTED
    artifact_uri: str | None = None
    seen_events: set[str] = field(default_factory=set)


def apply_event(job: Job, event: Event) -> bool:
    if event.event_id in job.seen_events:
        return False

    job.seen_events.add(event.event_id)
    if event.remote_job_id != job.remote_job_id:
        raise ValueError("event belongs to another job")
    if event.revision <= job.revision:
        return False

    if event.state == State.SUCCEEDED and not event.artifact_uri:
        raise ValueError("successful event requires an artifact")

    job.revision = event.revision
    job.state = event.state
    job.artifact_uri = event.artifact_uri if event.state == State.SUCCEEDED else None
    return True
Enter fullscreen mode Exit fullscreen mode

That function isn't a full webhook handler. Authentication, authorization, payload size limits, schema validation, secret rotation, and an atomic database transaction still belong at the boundary. The important design choice is more modest — acknowledge receipt only after durable insertion, then process outside the delivery request. If processing fails, the inbox row remains available for another worker; if acknowledgment is lost and delivery repeats, the event ID prevents duplicated work. Test the ledger with sequences, not isolated examples: send revision 2 before revision 1, deliver revision 2 twice, reuse an event ID with altered content and reject it at validation, and simulate a client losing the response to job submission before reconciling through the stable local request ID under the API's documented deduplication rules. Pause workers between receipt and processing as well. A callback that never arrives should lead to bounded polling and reconciliation, not a permanently spinning UI. This single sequence exercises transport uncertainty, event ordering, worker recovery, and downstream deduplication without pretending a successful demo request proves any of them.

Keep HTTP status codes boring and precise in your own receiver. A malformed or unauthenticated payload should not enter the inbox. A valid duplicate can be acknowledged without repeating downstream work. The exact retry response contract is provider-specific, so verify it in documentation and in a sandbox rather than guessing that every 4xx response stops delivery or that every timeout triggers the same schedule.

Long recordings need media and artifact boundaries

Identity comes first.

A remote job ID isn't enough provenance for support calls or podcasts. Record an immutable media identifier, byte length, content type, and a checksum computed before submission. Store consent and retention metadata beside the job, but keep raw media access separate from the transcript consumer. The transcript itself should be an immutable artifact with a schema version; corrected or reprocessed output becomes a new artifact, not an edit that silently changes past evaluations.

Chunking is a trade-off, not a default optimization. It can reduce the amount of work repeated after a failed segment and can expose partial progress, yet arbitrary cuts can damage words, speaker turns, and timestamps at the boundary. Prefer a provider's documented long-recording workflow when it preserves global timing and diarization. If the application must split media, use deterministic boundaries with overlap, retain the mapping back to source time, and test the stitcher on cross-boundary speech. The catch is that overlap can duplicate words and increase processed audio, so the merge policy belongs in both the cost model and the quality evaluation.

Support calls and podcasts also disagree about what “correct” means. Calls may put more weight on agent/customer attribution, account terms, and redaction. Podcasts may care more about names, chapter boundaries, and readable punctuation. One blended score can conceal a painful regression in either group — keep separate slices for recording type, duration band, channel layout, language, acoustic condition, and speaker count when those dimensions exist in your authorized evaluation data.

Quality, latency, and prompt cost belong in one experiment

Measure the pipeline.

Transcription evaluation needs frozen audio and reference text produced under a documented annotation policy. Measure word-level errors where that metric fits, but also score the fields that drive the application: speaker attribution, timestamps, named terms, redaction behavior, and the exact snippets retrieved by a downstream RAG system. A transcript can look readable and still break a support workflow because one order number changed; another can score worse globally while preserving every fact the agent needs.

Then replay the complete pipeline. Version the transcription settings, normalization code, retrieval configuration, and downstream prompt. The Prompt Engineering Guide is a useful public starting point for prompt methods, but a production decision still needs a workload-specific eval. Prompt-cost awareness matters here: repeated words from chunk overlap, verbose timestamp formats, and diarization labels all become tokens when the entire transcript is sent onward. Count input tokens and model calls per accepted artifact, alongside transcription usage, rather than treating summarization as free fallout from speech recognition.

The comparison table should remain small enough to populate with evidence instead of impressions:

Dimension Acceptance evidence Failure signal
Job identity Same local request maps to one intended job under the documented retry contract Ambiguous submission creates untracked work
Delivery Duplicate and out-of-order event tests converge on one terminal state State regresses or downstream work repeats
Artifact Polling and callback resolve to the same versioned result Mutable output invalidates an earlier eval
Speech quality Frozen, sliced corpus with task-specific checks Aggregate score hides a critical slice
End-to-end latency Submit-to-accepted-artifact distribution Fast callback masks slow or missing reconciliation
Total workload Audio processing plus retries, storage, polling, and downstream tokens Unit price ignores application overhead

Don't collapse these dimensions into one synthetic rank too early. Set hard gates first, such as no state regression and no duplicated downstream action in the retry suite. Compare quality and operating effort only among candidates that pass. Your mileage may vary on the weights; a support escalation workflow and a weekly podcast archive have different consequences when a transcript arrives late.

What to measure before copying this choice

Run a shadow batch large enough to contain the ugly shapes already present in your authorized corpus, then report distributions rather than a single average. Capture submission attempts per accepted job, duplicate deliveries, out-of-order deliveries, reconciliation lag, terminal jobs without an accepted artifact, processing time by duration band, artifact size, slice-level quality, downstream token count, and human-review rate. The exact sample size depends on traffic diversity and the error rate you need to detect; there isn't a credible universal number in the available public sources.

Ship only after forced retries and worker interruptions preserve the ledger invariants, and after the eval shows acceptable results independently for calls and podcasts. This approach is not suitable when the team cannot operate durable state or when recordings cannot be retained long enough for replay-based evaluation; in that case, narrow the workflow, use an approved ephemeral test set, or choose a simpler synchronous boundary whose documented limits match the input. The best batch API is the one whose contract your system can prove, observe, and afford as a complete transcription-to-text workflow.

References

Top comments (0)