Enterprise Integrations & Backend Engineering
Build resilient APIs, OAuth flows, verified webhooks, idempotent workers, streaming LLM backends, and multi-provider LLM gateways with budgets and metering.
Learning objectives
By the end of this chapter, you should be able to:
- turn a third-party API into an explicit, versioned contract, not a collection of happy-path calls;
- defend OAuth, service-account, and webhook admission controls for a multi-tenant connector;
- design end-to-end LLM streaming — SSE versus WebSockets, backpressure, mid-stream errors, resumable streams;
- architect an LLM gateway: routing, cross-provider fallback, token metering, budgets, safe caching;
- design token-denominated rate limits, quotas, and per-tenant cost attribution;
- run long-running agent work as durable async jobs with idempotent side effects; and
- present an HRIS connector and a gateway design as senior interview answers with failure evidence.
1. Treat every integration as a changing contract
An enterprise connector is a small distributed system at an organizational boundary: the remote team controls schema, quotas, release cadence, and incident response; your team owns the consequences. Write the contract you need first; isolate the vendor adapter behind it.
REST gives resources, caching, and operational visibility; GraphQL reduces over-fetching but still needs explicit query-cost, pagination, partial-error, and field-authorization handling. Neither removes the need for a canonical internal model: convert remote objects into stable internal types at the edge so provider renames do not spread.
| Contract concern | Decision to make | Failure if omitted |
|---|---|---|
| Identity | Which remote identifier is immutable? Is email only an attribute? | Renames create duplicates or overwrite the wrong record. |
| Pagination | Cursor, offset, or time window; stable ordering; page-size cap | Concurrent changes produce gaps or repeated pages. |
| Null versus absent | Does absence mean “unchanged,” “unknown,” or “clear the value”? | Partial updates erase valid data. |
| Versioning | URL/header version, compatibility window, schema capability | A vendor rollout breaks all tenants at once. |
| Error model | Machine-readable code, retryability, request ID, field errors | Workers retry permanent failures or drop transient ones. |
Design the public API around jobs
A sync that may take minutes should not hold an HTTP connection open. Validate authorization and idempotency, persist a job, enqueue it, return 202 Accepted with a status URL exposing queued → running → succeeded | partially_succeeded | failed | cancelled plus counts, timestamps, and a sanitized error summary. Cancellation is observed at safe checkpoints, not instant. Section 7 extends this shape to agent work.
POST /v1/tenants/{tenant_id}/sync-jobs
Idempotency-Key: 7f8f... # scoped to tenant + operation
202 Accepted
{ "job_id": "job_01...", "status": "queued", "status_url": "/v1/sync-jobs/job_01..." }
Prefer opaque cursors on a deterministic sort key; evolve with contract tests, tolerant readers for additive fields, and telemetry-backed deprecation. “We will version later” is not a strategy.
2. Identity, tenant boundaries, and webhook admission
Authentication proves who is calling; authorization decides what that identity may do; tenant routing decides whose data the action can touch. Keep all three visible.
Delegated user OAuth
When actions must reflect a human’s permissions and consent. Encrypt refresh tokens, request narrow scopes, bind the connection to a tenant, treat revoked consent as normal.
Service account
For tenant-wide unattended sync. Prefer workload identity or asymmetric client auth over long-lived shared secrets; separate credentials by environment and tenant.
OIDC login
When the app needs an authenticated user session. An ID token describes authentication; it is not an API authorization token.
The IETF baseline — authorization code with PKCE, exact redirect matching, mix-up and CSRF protection, sender-constrained tokens — is RFC 9700, not the original OAuth 2.0 RFC.
Webhook admission pipeline
flowchart LR
PR["Provider webhook"] --> SZ["Raw-byte size and content-type limit"]
SZ --> SG["Signature and freshness check"]
SG --> TN["Tenant lookup by endpoint secret"]
TN --> IB["Durable inbox insert (unique delivery id)"]
IB --> AK["Fast 2xx acknowledgement"]
IB --> WR["Async worker"]
WR --> DE["Domain event via outbox"]
- Read the exact raw bytes under strict size and type limits; never parse and re-serialize first.
- Select the secret by authenticated endpoint or connection identifier — never by a tenant ID from the payload.
- Verify the signature in constant time (GitHub’s guidance is canonical).
- Enforce a bounded age on signed timestamps and record delivery IDs — signature validity does not prove freshness.
- Insert metadata and payload hash into an inbox with a unique key, acknowledge fast, process asynchronously.
Accept both secrets during a short audited rotation overlap; record the key version per delivery. Never log tokens, secrets, or unredacted payloads: a correlation ID is useful, a credential is not.
3. Delivery semantics are end-to-end properties
Brokers describe transport behavior; the business outcome also depends on producers, consumers, databases, and external side effects. Say exactly where duplication or loss can occur.
- At-most-once: no retry after uncertainty; work may be lost, duplicates avoided. Only when loss is cheaper than duplication.
- At-least-once: retry until acknowledged; handlers must make duplicates harmless.
- Effectively once: idempotency, uniqueness, and atomic transitions make the result occur once within a defined boundary.
Kafka supports idempotent production and transactions within its own model (delivery-semantics docs), but do not stretch that across an arbitrary email, payroll API, and database. Define the transaction boundary and the compensation path.
Inbox, outbox, and idempotent effects
BEGIN;
INSERT INTO processed_event(tenant_id, event_id)
VALUES (:tenant, :event) ON CONFLICT DO NOTHING; -- duplicate becomes a no-op
-- Continue only if one row was inserted.
UPSERT employee ...;
INSERT INTO outbox(event_id, aggregate_id, event_type, payload) ...;
COMMIT;
The inbox stops repeated consumption from re-applying a transition. The outbox stores a domain event in the same transaction as the domain change; a relay publishes committed rows. That closes the dual-write gap but not duplicate publication — consumers still deduplicate (AWS transactional outbox guide). For non-idempotent external effects, use a provider idempotency key or a local operation record, and resolve uncertain outcomes by querying the provider before retrying. A client timeout never proves failure.
Ordering, backpressure, and dead letters
Global ordering is expensive and rarely required. Partition by the smallest aggregate needing order — often tenant_id + employee_id — with an aggregate version so consumers reject stale updates. Backpressure is a correctness control: bound worker concurrency, honor rate-limit hints, back off exponentially with full jitter (AWS Builders’ Library), and stop admitting backfills before real-time changes starve. A dead-letter queue is quarantine, not a cemetery: keep error class, attempt history, and a redacted payload reference; replay revalidates authorization and schema, rate-limits release, and audits itself.
4. Streaming LLM responses end-to-end
GenAI adds a transport problem the classic connector never had: a response that streams for tens of seconds, its perceived quality dominated by time-to-first-token (Chapter 2). The job is moving that stream across every hop unbuffered — and defining what the client sees when something dies at token 500.
| Dimension | Server-sent events (SSE) | WebSockets |
|---|---|---|
| Direction | Server → client, plain HTTP | Full duplex |
| Infrastructure | Normal HTTP through L7 load balancers; just disable buffering | Upgrade handshake; every proxy and WAF must support it |
| Reconnection | Built in: auto-retry plus a Last-Event-ID cursor (WHATWG spec) | You design your own resume protocol |
| Best fit | One-way token streams — chat and copilots | Voice, collaboration, client events mid-stream |
Choose SSE unless the client must talk during the stream; “stop generation” works as a POST to a cancel endpoint. Disable proxy buffering and compression on the route, flush per event, heartbeat every 10–15 seconds so idle timeouts (60 seconds default on an ALB) do not sever quiet streams; on disconnect, cancel the provider stream — you pay for every generated token, read or not.
sequenceDiagram
participant C as "Client"
participant E as "Edge and load balancer"
participant S as "App service"
participant P as "Model provider"
C->>E: POST chat request with stream enabled
E->>S: forward with deadline
S->>P: open provider token stream
P-->>S: token deltas
S-->>E: SSE events with event ids
E-->>C: flushed chunks per event
P-->>S: provider error mid stream
S-->>C: typed terminal error event then clean close
- Mid-stream errors — the 200 left at token one. Emit typed in-band events (
delta,error,done) with a mandatory terminal event; an abrupt close without one is an uncertain outcome, not success. - Backpressure — a slow client cannot pause Bedrock or Vertex. Await socket writes, bound the per-connection buffer, coalesce deltas or cancel at the bound.
- Resumable streams — persist deltas to a durable log keyed by response ID, batched every 50–100 ms; reconnects with
Last-Event-IDreplay from the offset, then continue live. - Usage finality — record provider-reported token usage exactly once at finalization, even if the client vanished.
5. The LLM gateway: one front door for every model provider
Once several teams call several models, put a gateway between products and providers — the pattern popularized by LiteLLM-style proxies. Applications speak one internal dialect and reference model aliases; the gateway owns everything a provider swap should not break.
flowchart LR
APP["Product services"] --> GW["LLM gateway"]
GW --> ADM["Virtual key auth + budget check"]
ADM --> CA["Exact and semantic cache"]
CA -->|"hit"| RES["Serve result"]
CA -->|"miss"| RTR["Router with model aliases"]
RTR -->|"primary"| BR["Bedrock"]
RTR -->|"fallback"| VX["Vertex AI"]
RTR -->|"fallback"| OA["OpenAI-compatible endpoint"]
BR --> MET["Token metering + usage events"]
VX --> MET
OA --> MET
MET --> RES
Routing is configuration: aliases like fast and reasoning map to provider, model version, and region (data-residency pins live here), with canaries gated by the Chapter 6 evaluation harness. Retries stay in-provider — jittered backoff on 429/5xx inside a latency budget, hedged on time-to-first-token. Fallback crosses providers and is a product decision: tokenizers, context limits, tool schemas, and behavior differ, so only eval-approved pairs enter a chain, responses carry the served model, and a circuit breaker keyed on provider + region + model trips it automatically.
Metering records provider-reported tokens — never your own estimate — with tenant, principal, feature, model, cache flag, latency, and priced cost. Caching starts exact-match: tenant scope, model version, system-prompt hash, normalized parameters, prompt. Semantic caching (embedding similarity over a threshold) multiplies hit rate but risks stale answers and cross-tenant leakage: scope by tenant and ACL hash, exclude personalized or tool-using calls, eval-check served quality. Keep the gateway boring — stateless, horizontally scaled, counters in Redis — it sits on every request path. Bedrock’s cross-region inference and intelligent prompt routing cover slices natively (Bedrock docs); a gateway earns its place once you span providers or need tenant policy.
6. Rate limiting and quotas for token-denominated APIs
Request-per-minute limits fail because one LLM request can cost three orders of magnitude more than another — a 300-token lookup versus a 150k-token document analysis (example figures). Providers meter requests and tokens separately (OpenAI’s rate-limit guide; Bedrock and Vertex quotas are analogous); your platform must do the same per tenant.
The pattern is estimate–debit–reconcile: count prompt tokens exactly, estimate completion from max_tokens or a per-feature historical p95, debit the tenant’s bucket at admission, reconcile against provider-reported usage after. Unbounded max_tokens means unbounded debit — force a cap. Fairness matters more than raw limiting: per-tenant buckets draw weighted shares of provider quota, headroom is reserved for interactive traffic over batch, and a noisy tenant is throttled before the provider throttles everyone.
When rejecting, behave like a good provider: 429 with Retry-After, distinguish exhausted tenant quota from degraded platform capacity, and offer degrade paths — a cheaper alias, truncated context, or conversion to an async job.
7. Async jobs for long-running agent work
Agent runs (Chapter 5 owns the loop) are minutes long and unpredictable — the Section 1 job pattern with more states. Intake validates, persists, enqueues, returns 202; workers lease with a visibility timeout beyond the worst step or heartbeat to extend; each step checkpoints so a crash resumes rather than restarts; cancellation is observed between steps.
stateDiagram-v2
[*] --> queued
queued --> running : worker lease
running --> awaiting_approval : gated tool call
awaiting_approval --> running : human approves
awaiting_approval --> cancelled : rejected or expired
running --> succeeded
running --> partially_succeeded : some steps failed
running --> failed : retries exhausted
queued --> cancelled : cancel requested
running --> cancelled : observed at checkpoint
succeeded --> [*]
partially_succeeded --> [*]
failed --> [*]
cancelled --> [*]
Deliver status per consumer: polling with backoff and ETags as the baseline; outbound webhooks — you are now the Section 2 provider, owing signed payloads, retries, a DLQ, and redelivery; an SSE status stream for interactive UIs, reusing Section 4’s machinery. For approvals, waits, and compensation, use a durable orchestrator: Step Functions callback task tokens and Workflows callbacks model waiting for a human without a worker burning a lease.
AWS
- API Gatewayfront door, usage plans, WebSocket APIs
- Lambdaintake and workers; response streaming for SSE
- SQSwork queue: visibility timeout, redrive, DLQ
- Step Functionsdurable orchestration, callback task tokens
- EventBridgecompletion fan-out; API destinations for webhooks out
Google Cloud
- Apigeegateway with quota and spike-arrest policies
- Cloud Runstreaming services and job workers
- Pub/Subwork distribution, push or pull, dead-letter topics
- Cloud Tasksrate-controlled dispatch, per-queue throttles
- Workflowsdurable orchestration with callbacks
SQS or Pub/Sub for single-step fan-out; Step Functions or Workflows for branches, waits, and compensation; Cloud Tasks for per-queue rate control toward fragile downstreams — Section 3’s admission control, applied outbound.
8. Idempotent AI actions and multi-tenant cost attribution
An agent that sends emails, files tickets, or issues refunds makes Section 3’s idempotency adversarial: the model may phrase the same intent with different text on every retry, so keys must derive from the action, not the words — hash(tenant, job, step, tool, canonical_args).
The effect journal records intent → executing → done-with-result. On any retry — including an LLM retry replaying a step whose tool already ran — the journal answers instead of the tool: the agent sees the recorded result rather than sending a second email. Uncertain outcomes resolve by querying the provider or via provider idempotency keys (Stripe’s design); unrepeatable actions get reserve/confirm phases or a human gate from Figure 4. Classify tools by side-effect risk at registration, not in the prompt.
flowchart LR
UE["Usage event per call"] --> MT["Metering stream"]
MT --> EN["Enrich with versioned price sheet"]
EN --> AG["Hourly rollup per tenant and feature"]
AG --> BE["Budget engine"]
BE -->|"soft limit"| DG["Alert and degrade to cheaper alias"]
BE -->|"hard limit"| HL["Reject new work with 429"]
AG --> SB["Showback dashboards and invoice export"]
Attribution requires every AI-adjacent call to emit a usage event — model calls, embeddings, vector queries, GPU seconds — priced at enrichment from a versioned price sheet so reports survive price changes. Soft budgets alert and degrade the alias; hard budgets reject new work while in-flight streams finish. Reconcile with the invoice monthly: Bedrock application inference profiles and cost-allocation tags on AWS; labels plus the BigQuery billing export on Google Cloud. Chapter 10 covers wider FinOps.
9. Worked system: a resilient HRIS-to-AI knowledge connector
A hypothetical interview-practice system, not a claim about Purnendu’s experience. A customer wants employee directory and policy documents synchronized from an HRIS into an access-controlled AI assistant. Minutes of staleness are acceptable; cross-tenant or cross-group exposure is not.
flowchart TD
H["HRIS provider"] -->|"OAuth + webhooks"| API["Connector API"]
API --> IB["Inbox"]
IB --> Q["Queue"]
Q --> WK["Bounded workers"]
WK -->|"paginated reads"| H
WK --> PG["Canonical PostgreSQL"]
PG --> OB["Outbox relay"]
OB --> IX["ACL-aware indexer"]
SCH["Scheduler"] --> SY["Incremental sync"]
SY --> WK
SY --> RC["Reconciliation + audit report"]
Key records: connection (tenant, provider, scopes, encrypted credential reference, health), sync_job (cursor, high-water mark, counts), inbox_delivery, source_object (remote ID, version, payload hash, tombstone), employee, outbox_event, sync_anomaly. Tenant-owned keys begin with tenant_id; repositories require tenant context. Sync captures a start high-water mark, pages deterministically, and advances the checkpoint only after durable apply. Webhooks are latency hints; reconciliation compares IDs, counts, and sampled hashes to catch missed webhooks, permission loss, and drift. Deletions become tombstones and removal events.
| Failure | Immediate behavior | Repair |
|---|---|---|
| Access token expires | Single-flight refresh; pause that connection only | Rotate and retry within deadline; terminal failure marks reauthorization_required |
| 429 or provider outage | Honor retry guidance, jittered backoff, open circuit | Resume from committed cursor; surface lag per tenant |
| Worker dies after commit | Broker redelivers | Inbox/unique keys make the repeated page harmless |
| Schema adds an enum value | Preserve raw value; map to unknown; emit anomaly | Update adapter, replay quarantined records |
| Index write uncertain | Do not complete the outbox event | Retry with deterministic document ID; reconcile DB against index |
| Tenant disconnects | Revoke credentials, stop new work | Cancel at checkpoints; run retention/deletion workflow with evidence |
Python backend judgment
Use async def only for genuinely asynchronous I/O; offload CPU-heavy parsing and blocking SDKs off the event loop (FastAPI’s async guidance). Bound concurrency with a semaphore, pass deadlines through, close clients in lifespan hooks, treat cancellation as control flow.
async with asyncio.timeout(job.remaining_seconds()):
async with provider_slots: # protects provider and this process
page = await client.list_people(cursor=job.cursor)
await repository.apply_page_atomically(page, job_id=job.id)
await repository.commit_checkpoint(job.id, page.next_cursor)
# Never catch BaseException and swallow CancelledError.
Validate payloads into strict adapter models (Pydantic), then map to domain types. Unit-test mappings; integration-test transactions and redelivery; contract-test fixtures; end-to-end test a fake provider injecting 429s, timeouts, malformed pages, cursor loops, and duplicate webhooks.
10. Backend breadth checkpoint
Senior interviews probe whether depth in one stack generalizes. The bar: production-strong Python plus the ability to read, implement, and critically review one secondary stack.
- API surface — REST and GraphQL both need authentication, tenant scoping, pagination, stable error contracts, abuse protection; version additively with deprecation telemetry.
- Queues are products — Kafka: partitioned log with replay and offsets. SQS: visibility timeouts, standard/FIFO. Pub/Sub: acknowledged delivery, subscription retention. State ordering, duplication, retention, and DLQ policy per product — never “a message broker.”
- Python service depth — dependency injection for principals, tenant context, sessions; durable work in queues, not fire-and-forget tasks; tests for duplicates, timeouts, cancellation.
- Secondary stack — Node/TypeScript: event loop, promise cancellation, runtime validation. Go: contexts, channels, resource ownership. Same semantics, different spelling.
Interview playbook
Lead with the business invariant, then trace one request and one failure. A compact structure is BOUNDARY:
- B — Business truth: source of truth, freshness, deletion, conflicts, success metric.
- O — Ownership and identity: tenant, principal, scopes, data classification, audit actor.
- U — Uncertainty: timeouts, duplicates, partial failure, ordering, unknown outcomes.
- N — Normalized contract: canonical model, API/job state, event envelope, versioning.
- D — Durability: inbox/outbox, checkpoints, idempotency boundary, effect journal, reconciliation.
- A — Admission control: token-aware limits, budgets, backpressure, deadlines, circuit breakers.
- R — Recovery and rollout: replay, DLQ, resumable streams, provider fallback, canary tenants.
- Y — Yardsticks: sync lag, completion rate, duplicates suppressed, cost per tenant, tokens per feature.
Classic traps: “exactly once” without a boundary, email as immutable identity, tenant ID from an unsigned payload, retrying every error, DLQ as recovery. GenAI traps: request-count limits on a token API, SSE through a buffering proxy, no terminal-event protocol, a semantic cache keyed without tenant or model version, silent cross-provider fallback inside an agent loop, tools with no idempotency story. When coding, narrate cancellation, transaction scope, and how a test proves duplicate safety.
Question bank
Practise aloud. Each answer should state assumptions and defend one concrete boundary.
Q1How do you prevent a duplicated webhook from creating two employee records?
Strong answer outline
- Durably insert the provider delivery ID under a tenant-scoped unique constraint.
- Map by immutable provider object ID; upsert with source version or payload hash.
- Commit inbox status, domain change, and outbox event atomically.
Follow-up probes
- No delivery ID from the provider?
- First request timed out after commit?
You covered transport deduplication, business idempotency, and the uncertain-outcome case.
Q2Design the OAuth lifecycle for a tenant-wide HRIS connection.
Strong answer outline
- Authorization code with PKCE, exact redirects, state and issuer validation, narrow scopes.
- Bind connection to tenant and installer; encrypt tokens, record scope and version, serialize refresh.
- Handle revocation, re-consent, rotation, and offboarding without leaking credentials.
Follow-up probes
- When is a service account preferable?
- How do two workers avoid refresh races?
You covered grant, storage, refresh, loss of access, and tenant binding.
Q3When is a retry unsafe?
Strong answer outline
- Validation and auth errors are terminal; throttling and transient failures may retry.
- After a timed-out non-idempotent write, query by idempotency key before writing again.
- Deadline, capped attempts, full jitter, shared retry budget.
Follow-up probes
- Why can retries amplify an outage?
- Where does
Retry-Afterinfluence scheduling?
You treated timeout as uncertainty and bounded retries across layers.
Q4Where should tenant isolation be enforced in a connector?
Strong answer outline
- Derive tenant context from the authenticated connection, never solely the payload.
- Carry tenant through queue envelope, repository API, keys, caches, metrics, audit.
- Add database policy constraints and adversarial cross-tenant tests.
Follow-up probes
- Risks in a shared worker cache?
- How does a dedicated-tenant deployment change this?
You enforced at every hop and named a test that attempts a leak.
Q5What does cancellation mean for an async job?
Strong answer outline
- Persist
cancel_requested; workers observe it before pages or side effects. - Propagate cancellation, close clients, release leases, keep the last checkpoint.
- Expose
cancelledonly after cleanup; compensate anything in flight.
Follow-up probes
- Why not kill the worker process?
- Cancellation during a database commit?
You distinguished request, observation, atomic boundaries, and terminal state.
Q6SSE or WebSockets for LLM tokens — and what happens when the provider dies at token 500?
Strong answer outline
- SSE by default: plain HTTP, built-in reconnect with
Last-Event-ID; WebSockets only when the client must talk mid-stream. - Hop discipline: no buffering, per-event flush, heartbeats, bounded buffers, cancel the provider stream on disconnect.
- The 200 is committed: typed in-band events with a mandatory terminal event, a durable delta log for replay-then-resume, usage recorded once at finalization.
Follow-up probes
- Abrupt close with no terminal event?
- Cost of the delta log per token?
You named the committed-status trap and a concrete resume mechanism.
Q7Your platform calls Bedrock, Vertex, and OpenAI. Design retry and fallback at the gateway.
Strong answer outline
- 429/5xx retry in-provider with jittered backoff, hedged on time-to-first-token; invalid or filtered requests never retry.
- Fallback only along eval-approved pairs — tokenizers, context limits, tool schemas differ; record the served model.
- Circuit-break per provider + region + model; cap fallback spend; log routing decisions.
Follow-up probes
- Why is silent fallback dangerous for a tool-calling agent?
- How do you test the chain before an outage?
You treated fallback as an eval-gated product decision, not an availability trick.
Q8Why do request-per-minute limits fail for LLM APIs, and what replaces them?
Strong answer outline
- Cost scales with tokens; meter requests, tokens, and concurrent streams independently.
- Estimate–debit–reconcile: debit prompt plus estimated completion tokens; settle with provider-reported usage.
- Per-tenant buckets with weighted fair shares; 429 with
Retry-After; degrade paths.
Follow-up probes
max_tokensunset or enormous?- Gateway enforcement or provider limits — why both?
You named both meters, reconciliation, and tenant fairness.
Q9An agent sends emails and creates tickets. A step times out and retries. How is the email sent once?
Strong answer outline
- Key from tenant + job + step + canonical args — never model text; journal intent before executing.
- On retry the journal answers: done replays the recorded result; uncertain outcomes resolve via the provider before re-issuing.
- Classify tools by side-effect risk; unrepeatable actions get reserve/confirm or human gates.
Follow-up probes
- Model re-plans with slightly different arguments?
- Journal retention and concurrency semantics?
You separated LLM retries from tool retries and defined journal replay.
Q10Design multi-tenant cost attribution for a GenAI platform.
Strong answer outline
- Usage event per model, embedding, and vector call — tenant, feature, model, provider-reported tokens — priced at enrichment from a versioned price sheet.
- Aggregate to per-tenant showback; soft budgets degrade, hard budgets reject, at the gateway.
- Reconcile with the invoice: Bedrock inference profiles and tags; GCP labels plus BigQuery billing export.
Follow-up probes
- Charge cache-served responses?
- What breaks if prices are looked up at report time?
You covered capture, price versioning, enforcement, and invoice reconciliation.
Q11Expose a five-minute agent run through your public API: polling, webhooks, or streaming?
Strong answer outline
202plus a job resource with an explicit state machine; polling with backoff and ETags as baseline.- Outbound webhooks make you the provider: signed payloads, retries, DLQ, redelivery; SSE status for UIs.
- Durable orchestration: heartbeat leases, per-step checkpoints, safe-point cancellation, approval callback tokens.
Follow-up probes
- How do consumers deduplicate your webhooks?
- When is a synchronous variant acceptable?
You chose per consumer type and carried admission duties outbound.
Q12When is a semantic cache safe in front of an LLM, and how would you build the key?
Strong answer outline
- Exact-match first: tenant scope + model version + system-prompt hash + normalized parameters + prompt.
- Semantic lookup risks stale answers and cross-ACL leakage: scope by tenant and ACL hash; exclude personalized or tool-using calls.
- Eval-check cache-served quality; invalidate on model or system-prompt change.
Follow-up probes
- Cache streaming responses?
- What hit rate justifies the infrastructure?
Your key included tenant, model version, and ACL scope — risk framed before savings.
Proof artifact: LLM gateway and resilient connector laboratory
Build a small gateway-plus-connector system against fake providers. All targets are example acceptance thresholds, not claims of prior results.
- Gateway coreFastAPI proxy over two fake providers with different dialects: virtual keys, model aliases, per-tenant estimate–debit–reconcile buckets, usage events to a metering table.
- Streaming pathSSE with heartbeats, typed delta/error/done events, Redis delta log with
Last-Event-IDresume. Kill a provider mid-stream; demonstrate recovery. - Fallback and cacheJittered in-provider retries, an eval-gated cross-provider fallback pair with a circuit breaker, an exact-match cache keyed by tenant + model version + prompt hash.
- Async agent job202-based job API with the Figure 4 state machine, signed outbound webhooks with retries and DLQ, an idempotent tool-effect journal.
- Connector spineWebhook inbox, checkpointed sync against a fake HRIS injecting 429s, duplicates, and cursor loops, plus outbox-driven indexing and reconciliation.
- Cost reportPer-tenant, per-feature showback; a budget breach that degrades the alias, then hard-rejects.
Measure: p50/p95 time-to-first-token through the gateway, stream resume success rate, fallback activations and quality delta, token-estimate error after reconciliation, duplicate effects across 1,000 replayed webhooks and tool retries (lab target: zero), reconciliation drift, cost-report accuracy.
Inject failures: provider dies at token 500; slow client stalls a stream; both providers 429 at once; worker crashes between tool execution and journal write; a webhook replayed 100 times; budget exhausted mid-stream; unknown enum from the HRIS.
Present: a two-minute walkthrough, one client-to-provider trace, the resume demo, before/after reconciliation and cost reports, and a decision record covering SSE versus WebSockets, the fallback gate, and one rejected alternative.
Chapter review
A production integration assumes change, duplication, delay, partial failure, and revoked authority; a GenAI backend adds streams that outlive their status code, costs denominated in tokens, and agents whose retries can repeat side effects. The core is unchanged: explicit contracts, durable state, idempotent effects, bounded and metered work, auditable tenant-aware recovery.
Glossary
- Inbox / outbox
- Durable tables that deduplicate received messages and atomically stage messages to publish.
- High-water mark
- Durable boundary showing how far an incremental process has safely advanced.
- Reconciliation
- Comparing source and destination truth to repair drift event delivery missed.
- Last-Event-ID
- SSE’s built-in resume cursor: the client replays from the last event it saw.
- Terminal event
- Mandatory in-band end-of-stream marker; its absence signals an uncertain outcome.
- Model alias
- Indirection between a capability tier and a concrete provider model version.
- Estimate–debit–reconcile
- Token-bucket lifecycle: debit an estimate at admission, settle with provider-reported usage.
- Effect journal
- Durable record of tool-call intent and result that answers retries instead of re-executing.
- Showback
- Per-tenant, per-feature cost reporting from priced usage events.
- Retry budget
- Bound on extra attempts so recovery traffic cannot overwhelm a degraded dependency.
Mastery checklist
- I can define an idempotency key’s scope, retention, and concurrency — for API calls and agent tool effects.
- I can trace tenant identity from ingress through queue, database, cache, usage event, and billing tag.
- I can explain why outbox publication and end-to-end exactly-once are different claims.
- I can design an SSE path with heartbeats, in-band terminal events, and Last-Event-ID resume.
- I can defend an eval-gated cross-provider fallback chain and say where it must never trigger.
- I can design token-denominated rate limits with estimation, reconciliation, and tenant fairness.
- I can produce a per-tenant cost report that survives a price change and reconciles against the invoice.
Primary sources
Links checked 2026-08-04.
- IETF RFC 9700 — OAuth 2.0 Security Best Current Practice
- WHATWG HTML Standard — Server-sent events
- GitHub Docs — Validating webhook deliveries
- AWS Prescriptive Guidance — Transactional outbox
- AWS Builders’ Library — Timeouts, retries, backoff with jitter
- AWS Lambda — Response streaming
- Amazon API Gateway — WebSocket APIs
- Amazon Bedrock documentation
- AWS Step Functions documentation
- Google Cloud Run — WebSockets
- Generative AI on Vertex AI documentation
- Google Cloud Workflows and Cloud Tasks documentation
- Google Cloud Billing — BigQuery export
- LiteLLM documentation
- OpenAI — Rate limits guide
- Stripe API — Idempotent requests
- Apache Kafka — Delivery semantics
- FastAPI — Concurrency and async/await