Some API operations take longer than a caller should have to wait on an open request. A bookstore partner's catalog export, for example, might take several minutes to build. Returning quickly is useful, but the partner still needs to know whether the service created the file, where to retrieve it, and what to do if an acknowledgment disappears. Moving the work into a queue answers none of those questions by itself.
Asynchronous API design connects a submission to an outcome the service delivers later.
This chapter compares polling, callbacks, message-based exchanges, and one-way submission, then explains the reliability decisions they share. The examples use a fictional bookstore service over HTTPS.
An asynchronous operation separates three moments: the service accepts responsibility for work, the work reaches an outcome, and the caller learns that outcome. These moments can be far apart. An export may finish while the partner is offline, or a notification may fail even though the file is ready.
Define the business completion condition before choosing a transport. For the bookstore, an export succeeds when the service stores the complete catalog file and makes it available through a download endpoint that checks authorization. Starting a worker or reading the last database row is insufficient.
The diagram shows how the same operation crosses separate boundaries:
A failure between any two boxes needs a recovery path. If the partner misses the acceptance response, it needs a way to find the operation it submitted. If delivery of the outcome fails, it needs to discover completed work without requesting another export.
The service's internal execution model and its public interaction pattern are separate choices. A queue-backed worker can serve clients that poll or receive callbacks. Conversely, a service can expose an asynchronous contract without a message broker by having workers claim pending work from durable storage.
Asynchrony helps when useful work can continue after the initial exchange ends. It does not reduce the work the service must do or guarantee a shorter completion time. For a small lookup whose answer the caller immediately needs, an ordinary synchronous response often remains easier to use.
With polling, the client periodically requests the current state of an accepted operation. It requires only outbound HTTP access and works for command-line tools, browsers, and partner systems that cannot receive inbound requests.
The bookstore accepts an export through POST /catalog-exports and exposes its state through a separate operation resource. That resource describes the work; the eventual catalog file is its result. Paths, JSON fields, status names, and duplicate-request rules below are this API's conventions.
All HTTP bodies in these examples are compact UTF-8 JSON without a trailing newline. The credentials are nonfunctional placeholders.
The API validates the request, checks permission, and durably records the work before responding:
HTTP 202 Accepted indicates that the server has accepted the request for processing but has not completed that processing. It does not promise eventual success. The original response will not later change into a success or failure response; learning the outcome requires another exchange.
This endpoint uses Location and status_url to identify its monitor. HTTP does not require this particular monitor contract. The application-defined poll_after_seconds field recommends a delay before the next status request; it is neither an estimated completion time nor a standard HTTP field.
The partner saves the operation ID and polls the returned relative URL on the same origin:
Once the file is ready, the response is:
While processing continues, the same GET returns 200 with status: "pending" or status: "running" and polling guidance. The HTTP status describes retrieving the operation representation; the body describes export execution. Clients stop polling when they see a terminal state, meaning an outcome that will not change for this operation.
Polling is simple to implement, but clients must make repeated requests and may notice completion late. With 12,000 active clients checking every five seconds, status traffic alone averages roughly 2,400 requests per second. Increasing the interval reduces traffic but makes completion take longer to notice. Jitter, or random variation in polling times, helps prevent synchronized bursts. Clients should also respect rate limits and stop automatic polling after their local waiting budget expires.
A local timeout stops waiting. It does not cancel the export. Keeping the operation ID lets the partner resume checking later.
A callback lets the provider initiate a separate request to a client endpoint when an outcome is available. A webhook is an HTTP callback that reports an event. This pattern fits partners that run an available server and want prompt completion notices without frequent polling.
For this bookstore, a partner can register an authorized callback destination and associate its identifier with an export submission. Registration binds the destination to the partner account. The submission does not accept an arbitrary URL for the worker to contact.
After recording success, the provider might deliver this JSON body to the registered endpoint:
This is an illustrative application payload, not a complete webhook security protocol. The receiver must verify the provider's authenticated delivery mechanism before trusting it. Relative status links in this contract refer to the bookstore API origin, not the receiver's host.
The callback can act as a prompt to retrieve authoritative state. This keeps notification payloads small and allows current access rules to govern result retrieval. It also creates a useful recovery path:
The acknowledgment means the receiver accepted the notification under the delivery contract. It does not prove that all downstream partner processing finished. The receiver should safely record the event before acknowledging if it promises to process it after a restart.
The provider may deliver notifications late or more than once. If all retries fail, the partner may never receive the notification. A partner that receives the same event_id twice should not import the file twice. A partner that receives nothing should still be able to check the operation. Publishing the notification after recording the outcome avoids announcing a result that the status endpoint cannot yet return under this example's consistency contract.
Callbacks add receiver availability, authentication, retry, and destination-management responsibilities. Use them when prompt notification matters enough to justify those responsibilities. Polling and callbacks can coexist: polling provides recovery, while callbacks reduce routine checking.
When both participants already use messaging infrastructure, the client can send a command and consume its result through a reply channel. A command asks a service to perform an action. A message broker transports messages between producers and consumers.
The export command could contain:
These fields are a hypothetical application envelope, not a broker-independent standard. The identity the broker authenticates determines which partner may publish the command. The service checks that the reply channel belongs to that partner and that its policy allows this operation to use the channel.
The eventual reply could be:
The correlation_id links the reply to the original command. Replies may arrive in a different order from commands, so the client cannot match them by position or arrival time. Saving the command ID before publication also lets a restarted client recognize a later reply.
A broker acknowledgment establishes only what the broker's documented acknowledgment mode promises, such as accepting or persisting a message. It does not establish that the export service validated the command or created the file. The service still needs to report whether it rejected or completed the command, through a reply or a status lookup.
Message-based exchange can accommodate temporarily disconnected consumers when the messaging system retains their messages. It also requires channel permissions, retention settings, duplicate handling, and operational ownership. A reply channel that expires before a slow operation finishes defeats that recovery path.
Choose messaging when the participating systems can support that infrastructure and its contract. Introducing broker access solely to let a public API client request one export can add more integration work than HTTP polling.
The request-and-reply pattern links an action to a particular caller. Publish/subscribe distributes messages to interested subscribers, often without the producer knowing which applications will consume them. An event reports a fact, such as catalog_export.succeeded; it does not ask a particular consumer to generate the export.
The bookstore might publish that event for an audit service and a partner synchronization service. Each consumer has its own processing outcome. Successful event publication does not mean both consumers have completed their work. Keep export completion separate from subscriber completion unless the business contract explicitly requires coordination across them.
A one-way submission offers no per-operation business result to the sender. People sometimes call this fire-and-forget, but the phrase hides an important choice: does the sender receive an ingestion acknowledgment, and what does that acknowledgment guarantee?
Usage telemetry may tolerate eventual aggregation without individual receipts. A catalog export normally cannot, because the partner needs its file or a failure explanation. Do not remove the outcome channel merely to make the initial request faster.
One-way does not necessarily mean unreliable. A service can promise durable ingestion and bounded retries without exposing individual results. Equally, a best-effort telemetry endpoint might deliberately permit loss. State that distinction explicitly, and give operators a summary of failures.
All of these patterns need to distinguish rejection before acceptance from failure during execution. The bookstore accepts only csv and json export formats. The service rejects a well-formed request specifying xml before creating any operation:
A valid submission from an authenticated partner without export permission instead receives:
Neither rejection creates background work. Validating and authorizing before acceptance gives callers useful feedback without making them poll for errors the service already knows about when the client submits the request.
Later failures remain possible. Suppose a different, accepted operation cannot read a required source after it uses all its internal retries. Its status GET succeeds and returns this representation:
A failed export is a known business outcome. A failed status request is an inability to observe the outcome. Clients should not start another export merely because the status endpoint temporarily returns an error.
The bookstore promises that accepted work survives an ordinary process restart. To meet that promise, it records both the operation and the intent to dispatch it before returning acceptance.
An unsafe implementation writes an operation row, returns 202, and then attempts an unrecorded queue send. A crash can leave the partner watching an operation that no worker will ever receive.
One implementation uses a transactional outbox: a dispatch record that the service commits in the same database transaction as the operation. A separate dispatcher publishes that record and retries publication as necessary. The durable record closes the gap between accepting work and arranging execution. It does not make delivery exactly once; a retry can still publish twice.
In this example, Idempotency-Key is a required application contract whose keys apply only to the authenticated partner and submission endpoint. The same key with equivalent validated input returns the same operation. Different input with that key returns 409 with idempotency_key_reused.
The service retains this mapping while work is active and for seven days after it finishes. During that period, the client can recover from a lost initial response as follows:
Concurrent replays must converge on one operation through an atomic uniqueness check; a separate lookup followed by an unguarded insert is insufficient. After retention expires, a replay may create new work, so the client cannot assume indefinite duplicate protection.
Submission deduplication and worker deduplication address different failures. A worker can finish writing a file and crash before acknowledging its message. Redelivery must recover that result or safely repeat execution for the same operation. Merely adding an idempotency key at the HTTP boundary does not solve that gap.
Use separate identifiers for separate purposes. An operation ID identifies accepted work, an idempotency key identifies repeated submission intent, and an event ID identifies a notification. Correlation links messages; it does not itself prevent duplicate side effects.
Select the outcome channel around the client's capabilities and the business need:
For the bookstore, polling is a practical baseline because every authorized partner can use it. Registered callbacks can reduce checking for partners that need faster discovery. Internal event subscribers can react to completed exports without changing the partner's result contract.
Capacity still limits acceptance. If workers can finish 20 exports per second while clients submit 30 per second, the backlog grows by roughly 600 exports per minute while those rates persist. A fast acceptance endpoint can hide that deterioration until users see hours of waiting.
Bound active operations per partner, total backlog, payload size, and worker concurrency. Reject excess work before promising acceptance when capacity is unavailable. Track queue wait time and end-to-end completion latency alongside initial response latency. A low latency for 202 responses says little about when users receive files.
Authorization must also survive the separation in time. Bind each operation to its submitting partner, check access on every status and file request, and define how permission revocation affects queued execution. This example rechecks export permission before processing and does not create a file after the partner loses that permission. Worker service credentials must not expand the partner's permitted data scope.
Keep status and result retention explicit. Here, terminal operation records and generated files remain available for seven days after completion. A client should save needed results before that window ends. An unavailable record afterward is not evidence that the original operation never ran, and callback delivery does not extend retention automatically.
Finally, define data timing. For this example, the worker reads a consistent catalog snapshot that it takes when execution begins. Acceptance does not freeze the catalog. If a partner needs an export of a specific revision, the request must identify that revision and the worker must use it throughout execution. Moving work into the background makes such timing decisions more visible because the input data can change while work waits.
An asynchronous API needs a clear acceptance promise, an identifiable unit of work, and a way to learn or recover its outcome. Polling, callbacks, message replies, and events serve different client capabilities; one-way submission fits only when individual results are unnecessary.
Make retries converge on the original work, preserve accepted intent across failures, and separate execution success from notification delivery. Limit how much work the API accepts, check access when clients return later, and document how long results remain available and when the service reads input data so callers can recover without guessing what happened.