---
title: "12 — API Integration Patterns: Sync, Async, Events, Gateways, Queues"
uid: api-integration-patterns
tags: ["eda", "integration", "messaging", "webhooks", "api-gateway", "async", "bff", "roadmap:api-design", "microservices"]
excerpt: "Integration patterns are three decisions in disguise: who waits for whom, who pushes at whom, and where the seams between services fall. Sync vs async, events vs polling, gateways vs BFFs — each optimizes a different coupling."
date: 2026-08-13T03:28:32+0000
source: https://www.aveshina.my.id/en/blog/api-integration-patterns
---

Every service-to-service integration I'd built seemed to invent its own shape — until I noticed all the shapes were answers to the same three questions. The model that finally clicked: **integration patterns are choices about three things — who waits for whom (sync vs async), who pushes data at whom (events vs polling vs webhooks), and where the seams between services fall (gateways, microservices, BFFs, queues).** [1] The patterns aren't interchangeable; each one optimizes for a different coupling and a different failure mode.

The thread connecting these nodes is *coupling and timing*. A synchronous call tightly couples two services in time — both must be up, simultaneously, for the call to succeed. An asynchronous pattern decouples them — one publishes, the other consumes later, and a queue in between absorbs the difference. Every pattern in this section is a position on that coupling/timing axis, chosen based on which failure modes you can tolerate.

## Synchronous vs asynchronous APIs

The first split is the most fundamental [2]. A **synchronous API** holds the connection open and waits for a response — the caller blocks until the work is done. GET /orders/42 returning the order is synchronous: the client waits, the server computes, the response comes back. This is simple to reason about but breaks down for long-running work — the caller is stuck, the connection is held, and a slow operation cascades into timeouts everywhere.

An **asynchronous API** doesn't make the caller wait for the result. The common shapes:

- **Fire-and-track:** POST /jobs returns 202 Accepted with a job ID immediately; the client polls /jobs/{id} (or gets a webhook) for the result.
- **Event-driven:** the operation is published as an event; whoever cares consumes it whenever.

```
HTTP/1.1 202 Accepted
Location: /jobs/abc123

// later, the client checks:
GET /jobs/abc123 → { "status": "complete", "result": {...} }
```

The rule I use: **if the work is fast, synchronous.** The simplicity is worth it. **If the work is slow, unreliable, or involves a downstream that might be down, asynchronous** — return 202, hand off the work, and let the client retrieve the result later. Forcing a 30-second operation into a synchronous call is how you get cascading timeouts.

## Event-driven architecture

**Event-Driven Architecture (EDA)** is the asynchronous pattern taken to its logical extreme [3]. Services don't call each other directly; they **publish events** ("order created", "payment received") and **react to events** they care about. The producer doesn't know — and doesn't need to know — who consumes. This decoupling is EDA's whole appeal: services can be added, removed, or changed without the producer caring, because the producer's only job is to announce what happened.

The tradeoff is a shift in how you think. In a synchronous world you trace a call chain ("A calls B calls C"). In an event-driven world you trace a flow of causality ("A published X, which triggered B, which published Y, which triggered C"). That flow is harder to follow when debugging, because no single call stack captures it. EDA is the right choice when you have many independent reactions to the same facts — "when an order is placed, update inventory, send a confirmation, trigger fulfillment, log to analytics" — and you don't want the order service to know about all of them.

## API gateways

An **API gateway** is a single entry point that sits in front of a set of backend services [4]. Instead of clients calling a dozen services directly, they call the gateway, which routes each request to the right backend, and handles cross-cutting concerns in one place: authentication, rate limiting, request/response transformation, logging, and sometimes request composition (fanning out to multiple services and stitching the responses together).

```figure
<svg viewBox="0 0 680 240" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="An API gateway sitting between clients and three backend microservices. Clients on the left send requests to the gateway; the gateway authenticates, rate-limits, and routes each request to one of orders, users, or billing services. A side label lists cross-cutting concerns handled at the gateway.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <defs>
      <marker id="gt" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
        <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
      </marker>
    </defs>

    <!-- clients -->
    <rect x="20" y="90" width="90" height="50" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.4"/>
    <text x="65" y="119" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">clients</text>

    <!-- gateway -->
    <rect x="240" y="60" width="160" height="120" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="320" y="86" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">API Gateway</text>
    <g font-size="10" fill="#1e1b4b" text-anchor="middle">
      <text x="320" y="108">auth</text>
      <text x="320" y="124">rate limit</text>
      <text x="320" y="140">routing</text>
      <text x="320" y="156">logging</text>
    </g>

    <!-- services -->
    <rect x="520" y="40" width="120" height="40" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.3"/>
    <text x="580" y="64" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">orders</text>
    <rect x="520" y="100" width="120" height="40" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.3"/>
    <text x="580" y="124" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">users</text>
    <rect x="520" y="160" width="120" height="40" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.3"/>
    <text x="580" y="184" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">billing</text>

    <!-- arrows -->
    <path d="M110,115 L238,115" stroke="#64748b" stroke-width="1.4" marker-end="url(#gt)"/>
    <path d="M400,90 L518,62" stroke="#64748b" stroke-width="1.3" marker-end="url(#gt)"/>
    <path d="M400,120 L518,120" stroke="#64748b" stroke-width="1.3" marker-end="url(#gt)"/>
    <path d="M400,150 L518,178" stroke="#64748b" stroke-width="1.3" marker-end="url(#gt)"/>
  </g>
</svg>
```

The gateway is the natural home for the non-business logic every endpoint needs. Without one, every backend service re-implements auth and rate limiting; with one, those concerns live in a single place and the backends focus on their actual job. In microservices architectures a gateway is near-essential — it's what lets clients talk to "the API" rather than to a dozen internal URLs.

## Microservices architecture

**Microservices** break a monolith into small, independently deployable services, each owning a slice of business capability, communicating through APIs (or events) [5]. The benefit is organizational and operational: teams own services end-to-end, deploy them independently, and scale them separately. The cost is distributed-systems complexity — network calls instead of function calls, eventual consistency instead of transactions, and the need for patterns like the gateway, the queue, and the BFF (next) just to keep the surface manageable.

The model I hold: microservices are a trade of *implementation simplicity* for *deployment and team scalability*. A monolith is simpler to build and debug; microservices let many teams move fast without stepping on each other. Reaching for microservices before the org actually needs them trades real complexity for hypothetical scale. Most APIs start as a monolith and extract services when (and only when) a real boundary justifies it.

## Webhooks vs polling

These are the two ways a client learns about changes on the server [6]. **Polling** is the client repeatedly asking "anything new?" — simple, but wasteful (most polls return nothing) and latent (the update arrives only on the next poll). **Webhooks** flip it: the client registers a URL, and the server pushes an HTTP request to it when something happens. Efficient and immediate, but the client must now run a server to receive the webhook, handle retries when that server is down, and verify the webhook's authenticity (anyone can POST to a URL).

The rule: **poll for simplicity when changes are infrequent and latency is tolerable; use webhooks when changes matter and the client can receive them.** [6] A script that checks a status once an hour is fine polling. A payment provider notifying a merchant of a charge needs a webhook.

## The BFF pattern

The **Backend for Frontend (BFF)** pattern is a specialized gateway: instead of one general-purpose API serving every client, you build a thin, dedicated backend *per client type* — one for the web app, one for mobile, one for third-parties [7]. Each BFF is tailored to exactly the data shape and interactions its frontend needs, which kills over-fetching and lets each frontend team evolve independently.

The motivation is directly tied to GraphQL's pitch (client-declared shape) but solved differently: instead of one flexible query language, you write purpose-built thin APIs. BFFs are the right call when clients are genuinely different (mobile vs web vs public partners) and a single API shape can't serve them all without over-fetching. The cost is running more services, so the pattern earns its keep when client divergence is real.

## Batch processing

**Batch processing** packs many operations into one request [8]. Instead of a client making 100 individual API calls, it sends one batch request with 100 operations, and the server processes them as a group. The wins are reduced connection overhead, fewer auth checks, and often atomicity (all-or-nothing semantics). The cost is API complexity — error handling for partial failures, ordering, size limits. Batch is the right tool for bulk data import/export and high-volume data-intensive workloads; it's overkill for a single operation.

## Messaging queues

A **message queue** is the infrastructure that makes asynchronous and event-driven patterns work at scale [9]. A **producer** sends a message to the queue; a **consumer** pulls messages off at its own pace. The queue is a buffer — it absorbs spikes, decouples producer and consumer timing, and survives consumer downtime (messages wait in the queue until the consumer recovers).

```
producer → [ message queue (buffer) ] → consumer
              decouples timing, absorbs spikes, retries failures
```

The benefits are the backbone of reliable distributed systems: **scalability** (add more consumers to drain the queue faster), **fault tolerance** (a failed message can be retried or dead-lettered), and **decoupling** (the producer doesn't wait for or know the consumer). When an operation needs to happen reliably but not synchronously, a queue is almost always the right answer. (The next post covers specific queue technologies — Kafka, RabbitMQ — in more depth.)

## How I use this

When connecting services, I ask the timing question first: does the caller need the result now (synchronous) or later (asynchronous)? For multiple independent reactions to the same fact, I reach for events (EDA) rather than wiring the producer to every consumer. For cross-cutting concerns across many services, I put an API gateway in front. When client types diverge enough that one API shape hurts them all, I consider a BFF. And for anything that must happen reliably but not immediately, I put a queue in the middle. The unifying habit is making the coupling/timing choice explicit — defaulting to synchronous calls everywhere is how systems become brittle, because every service becomes critically dependent on every other being up, right now.

## References

[1] DZone, "API Integration Patterns," 2024. [Online]. Available: [https://dzone.com/refcardz/api-integration-patterns](https://dzone.com/refcardz/api-integration-patterns)

[2] Nordic APIs, "The Differences Between Synchronous and Asynchronous APIs," 2024. [Online]. Available: [https://nordicapis.com/the-differences-between-synchronous-and-asynchronous-apis/](https://nordicapis.com/the-differences-between-synchronous-and-asynchronous-apis/)

[3] Microsoft Azure, "Event Driven Architecture Style," 2024. [Online]. Available: [https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven](https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/event-driven)

[4] Red Hat, "What does an API Gateway do?," 2024. [Online]. Available: [https://www.redhat.com/en/topics/api/what-does-an-api-gateway-do](https://www.redhat.com/en/topics/api/what-does-an-api-gateway-do)

[5] Google Cloud, "What is Microservices Architecture?," 2024. [Online]. Available: [https://cloud.google.com/learn/what-is-microservices-architecture](https://cloud.google.com/learn/what-is-microservices-architecture)

[6] Merge, "Polling vs webhooks: when to use one over the other," 2024. [Online]. Available: [https://www.merge.dev/blog/webhooks-vs-polling](https://www.merge.dev/blog/webhooks-vs-polling)

[7] bff-patterns.com, "Backend for Frontend," 2024. [Online]. Available: [https://bff-patterns.com/](https://bff-patterns.com/)

[8] Tyk, "API Design Guidance: Bulk vs Batch Import," 2024. [Online]. Available: [https://tyk.io/blog/api-design-guidance-bulk-and-batch-import/](https://tyk.io/blog/api-design-guidance-bulk-and-batch-import/)

[9] Amazon Web Services, "What is a Message Queue?," 2024. [Online]. Available: [https://aws.amazon.com/message-queue/](https://aws.amazon.com/message-queue/)

```quiz
Q: An operation takes 30 seconds and involves a downstream that's sometimes down. The right API shape is…
- synchronous — block the caller until it finishes
- asynchronous — return 202 with a job ID, let the client retrieve the result later
correct: 1
explain: Slow or unreliable work shouldn't hold a connection open. Return 202 Accepted immediately and hand the work off; the client tracks it via the job ID.

Q: What's the core benefit of event-driven architecture (EDA)?
- Producers and consumers are decoupled — the producer just announces events and doesn't know who consumes
- It removes the need for any network calls
correct: 0
explain: EDA decouples services via events. The producer doesn't depend on specific consumers, so new reactions can be added without touching the producer.

Q: An API gateway primarily exists to…
- centralize cross-cutting concerns (auth, rate limiting, routing, logging) in front of backend services
- replace all your backend services with one big service
correct: 0
explain: The gateway is a single entry point that handles the concerns every endpoint needs, so backends can focus on business logic.

Q: Why build separate BFFs (Backend for Frontend) for web and mobile instead of one general API?
- Each client type gets a tailored API shape, reducing over-fetching and letting frontend teams evolve independently
- BFFs are always faster than a single API
correct: 0
explain: When clients diverge enough, one shape hurts them all. A per-client BFF gives each the exact shape it needs.

Q: A message queue sits between a producer and a consumer. Its main jobs are to…
- buffer messages, decouple their timing, and survive consumer downtime with retries
- encrypt messages at rest
correct: 0
explain: The queue absorbs spikes, lets producer and consumer run at different paces, and holds messages if the consumer is down. That's the backbone of reliable async systems.
```
