Financial-services platforms rarely fail because REST or event streaming is inherently the wrong technology. They fail when the interaction model does not match the business operation.
A balance lookup needs an immediate answer. A transfer request needs a clear acceptance decision. Settlement may take minutes or days. Fraud, notifications, reporting, and reconciliation may all need to react independently after a transaction changes state.
Trying to handle every interaction with synchronous APIs creates long dependency chains and fragile workflows. Publishing every interaction as an event creates ambiguity around ownership, completion, and user-facing responses. Most production systems therefore need both patterns, with explicit rules for where each belongs.
Start with the interaction, not the transport
REST and events are often compared as if they were interchangeable protocols. They usually represent different interaction semantics.
A synchronous REST call asks another service to perform work or return information now. The caller waits for a response and knows whether the request was accepted, rejected, or failed to complete within the allotted time.
An event states that something has already happened. Publishers do not normally wait for every interested consumer, and consumers can process the event independently.
That distinction produces three useful categories:
- Queries request information without intending to change state, such as retrieving an account balance.
- Commands ask a specific owner to change state, such as placing a hold on funds.
- Events report completed facts, such as
FundsHoldPlacedorTransferRejected.
Commands can be sent over HTTP or asynchronous messaging. Events can be delivered through brokers, streams, or webhooks. The architectural decision is broader than choosing a wire format.
Where synchronous REST fits
REST is a strong fit when a caller needs an immediate, authoritative response and the work can finish within a bounded time.
Queries that require current information
Consider a channel application displaying whether funds are available before confirming a payment. A direct query to the service that owns the relevant balance can provide a clear result:
GET /accounts/acc-123/available-balance
Authorization: Bearer <token>
{
"accountId": "acc-123",
"currency": "GBP",
"available": "1250.00",
"asOf": "2025-03-08T10:15:30Z"
}
The timestamp matters. A financial value without freshness semantics can be misleading, especially when replicas, caches, or projections are involved.
REST is also appropriate for low-latency validation, reference data, and operator interfaces where a person needs an immediate answer. However, a query does not reserve the state it observes. Funds available during a balance check may be spent before a later transfer command. Business invariants must be enforced when the command is committed, not inferred from an earlier read.
Commands with immediate acceptance decisions
A synchronous command is useful when the caller needs to know whether the system of record accepted a state transition. For example, a funds service may atomically validate an account and place a hold before replying.
The request should include an idempotency key so that a client can safely retry after a timeout:
POST /funds-holds
Idempotency-Key: 7f4c2f2a-9c2e-4b21-a5e5-88df5bcf6df4
Content-Type: application/json
{
"accountId": "acc-123",
"amount": "75.00",
"currency": "GBP",
"reference": "order-456"
}
The server stores the key with the outcome of the operation. Reusing the same key and equivalent request returns the original result rather than creating a second hold. The implementation must define retention, request-mismatch behavior, and whether keys are scoped by client or operation.
The limits of synchronous chains
A REST call is simple; a chain of REST calls is not. If a payment service synchronously invokes account, fraud, sanctions, limits, notification, and reporting services, total latency and failure probability grow with every dependency.
flowchart LR
Client --> PaymentAPI
PaymentAPI --> AccountService
PaymentAPI --> FraudService
PaymentAPI --> ScreeningService
PaymentAPI --> NotificationService
This design also creates ambiguous outcomes. If the client times out, it may not know whether the payment was committed. If notification fails after funds are reserved, rolling back the financial operation may be inappropriate. Retries can amplify load during an incident and create duplicate side effects unless every boundary is idempotent.
Synchronous calls should therefore be reserved for dependencies that are required to make the immediate decision. Work that can occur after commitment is often better triggered asynchronously.
Where event-driven integration fits
Events are effective when one state change must inform multiple consumers, when processing can be deferred, or when a workflow spans independently operated systems.
Fan-out without publisher coupling
After a transfer is accepted, several capabilities may need the result:
- customer notifications;
- fraud model features;
- accounting or ledger projections;
- regulatory reporting feeds;
- operational analytics;
- reconciliation processes.
The transfer service should not need to know every consumer. It can publish a stable business event, and consumers can evolve independently.
flowchart LR
TransferService --> EventBroker
EventBroker --> NotificationConsumer
EventBroker --> ReportingConsumer
EventBroker --> ReconciliationConsumer
EventBroker --> AnalyticsConsumer
This decouples availability: a reporting outage does not have to block transfer acceptance. It does not eliminate coupling altogether. Consumers still depend on event meaning, schema, ordering rules, retention, and delivery guarantees.
A useful event describes a business fact rather than exposing an internal database row:
{
"eventId": "evt-8c31",
"eventType": "TransferAccepted",
"occurredAt": "2025-03-08T10:15:31Z",
"aggregateId": "trf-789",
"aggregateVersion": 3,
"data": {
"transferId": "trf-789",
"amount": "75.00",
"currency": "GBP",
"status": "ACCEPTED"
}
}
Identifiers, timestamps, versions, and explicit monetary representation help consumers process and audit the fact. Sensitive account or customer data should not be added merely for convenience; events are commonly retained and replicated more broadly than API responses.
Long-running workflows
Many financial processes cannot complete inside an HTTP timeout. A transfer may move through screening, reservation, submission to an external rail, confirmation, and settlement. These steps may involve retries, manual review, or waiting for an external party.
The initial API can validate the request, persist it, and return an operation identifier. 202 Accepted is appropriate when processing has begun but no final outcome exists. The client can query status or receive a callback when the state changes.
sequenceDiagram
participant C as Client
participant T as Transfer Service
participant B as Event Broker
participant W as Workflow
participant R as Payment Rail
C->>T: Submit transfer
T-->>C: Accepted with transfer ID
T->>B: TransferRequested
B->>W: Deliver event
W->>R: Submit instruction
R-->>W: Confirmation later
W->>B: TransferSettled
C->>T: Get transfer status
T-->>C: Settled
A workflow can be orchestrated, where one component tracks state and issues commands, or choreographed, where services react to events without a central coordinator. Choreography reduces central control but can make complex paths difficult to understand. For workflows with deadlines, compensation, manual intervention, or many branches, explicit orchestration is often easier to operate and audit.
Compensation is not the same as a database rollback. Releasing a hold, reversing a posting, and marking a transfer failed are new business actions with their own authorization and audit requirements.
Eventual consistency without losing financial correctness
Event-driven systems usually introduce eventual consistency between the source of truth and downstream views. That is acceptable only when the consistency boundary is intentionally chosen.
A ledger posting and its balancing entries may require one local atomic transaction. A reporting projection based on those postings may lag. The core invariant remains strongly enforced even though derived views are eventually consistent.
This leads to a critical rule: do not distribute an invariant merely to adopt events. If debits and credits must balance atomically, keep that decision inside one transactional boundary or use a design that preserves the invariant by construction.
User interfaces and APIs should expose consistency honestly. A transfer response can distinguish PENDING, ACCEPTED, SETTLED, FAILED, and REVERSED rather than presenting acceptance as settlement. Read models can include an asOf value or processing status. Where read-after-write behavior is required, route the query to the authoritative store, update the local projection before responding, or wait for a known version with a strict timeout.
Reliable publication and consumption
The hardest event-driven failure often occurs between committing business data and publishing its event. Writing to the database and broker as separate operations creates two bad outcomes: committed data with no event, or an event for data that was not committed.
The transactional outbox pattern addresses this by writing the business change and an outbox record in the same database transaction:
BEGIN;
UPDATE transfers
SET status = 'ACCEPTED', version = version + 1
WHERE transfer_id = :transfer_id;
INSERT INTO outbox_events (event_id, aggregate_id, event_type, payload)
VALUES (:event_id, :transfer_id, 'TransferAccepted', :payload);
COMMIT;
A separate publisher reads the outbox and sends events to the broker. Publication can happen more than once if the publisher crashes after sending but before recording completion. Consumers must consequently be idempotent.
A consumer can record each eventId in the same transaction as its side effect, use an inbox table, or perform an idempotent upsert keyed by business identity. Broker-level exactly-once features do not automatically make database writes, emails, or external payment submissions exactly once. End-to-end correctness still depends on application design.
Ordering also requires precision. Global ordering is expensive and usually unnecessary. Ordering by transfer or account is often sufficient, provided related events use the same partition key. Consumers should detect duplicate, stale, or missing aggregate versions rather than assuming delivery is perfectly ordered.
Operational trade-offs
REST centralizes complexity in request paths; events move complexity into asynchronous operations. Neither removes it.
| Concern | Synchronous REST | Event-driven integration |
|---|---|---|
| Response | Immediate success or failure | Acceptance now, outcome later |
| Coupling | Caller knows callee | Publisher knows event contract |
| Availability | Dependency failure affects request | Consumers can recover independently |
| Consistency | Easier read-after-write semantics | Commonly eventual across services |
| Fan-out | Requires multiple calls or an intermediary | Natural with multiple subscriptions |
| Failure handling | Timeouts, retries, circuit breakers | Redelivery, deduplication, dead letters |
| Debugging | Request traces are intuitive | Requires correlation across events |
| Capacity | Backpressure often appears as latency | Brokers buffer work, creating lag |
An event platform needs monitoring for consumer lag, oldest unprocessed message, retry rates, dead-letter queues, partition skew, schema compatibility, and replay progress. Teams also need procedures for poison events and safe replay. Reprocessing a notification is not equivalent to reprocessing a ledger posting.
REST operations need explicit deadlines, bounded retries, circuit breakers, concurrency limits, and protection against retry storms. A caller should retry only operations that are idempotent or protected by an idempotency key, using backoff and jitter.
Both styles require distributed tracing and correlation identifiers. For events, preserve causation and correlation metadata without using it as a substitute for business identifiers. Audit records should capture the authenticated actor, business action, decision, and relevant versions, not just infrastructure logs.
A practical hybrid architecture
For many financial systems, the most robust default is:
- Use REST for client-facing queries and commands requiring immediate validation.
- Commit authoritative state within the owning service's transaction boundary.
- Publish resulting business events through an outbox.
- Use events for fan-out, projections, integration, and long-running workflows.
- Provide a status resource for operations whose final outcome is asynchronous.
Choose synchronous REST when the caller cannot proceed without the answer, the dependency owns the required decision, and latency is predictably bounded. Choose events when multiple consumers need the fact, temporary consumer unavailability should not block the producer, or processing naturally continues over time.
Before deciding, ask:
- Is this message a request to act, a request for information, or a fact?
- Which service owns the invariant and authoritative state?
- Must the caller know acceptance or final completion?
- What happens when the response or event is delivered twice?
- What ordering is actually required?
- How stale may each read model be?
- Can the operation be replayed safely?
- Which data may be retained in the event stream?
- How will operators identify and repair a stuck workflow?
Conclusion
REST is best used for bounded, immediate interactions; events are best used for durable facts, fan-out, and asynchronous progress. Financial correctness depends less on the transport than on explicit state ownership, transactional boundaries, idempotency, observable workflows, and honest consistency semantics.
A well-designed system does not force one pattern everywhere. It uses synchronous commands and queries at the edges, preserves critical invariants close to authoritative data, and publishes events to coordinate everything that can safely happen afterward.



