Thuta Learning
ProjectsWeb Developmentintermediate

Project: A Complete Third-Party Integration

What you'll walk away with

  • Explain the core ideas behind Project: A Complete Third-Party Integration
  • Read the diagram and trace how a request, response, or event flows through the system
  • Explain how this applies to a real API integration you might build

Build the mental model

This capstone puts every reliability piece from this course into one pipeline: your frontend needs data from a third-party API, but the secret key must never reach the browser, so your backend sits in the middle as a proxy.

Secret management means the credential lives in the backend's environment and gets attached server-side -- and never appears in a log line even when something goes wrong.

Response validation means a 200 status alone is not proof of success -- the body's shape must be checked before you trust it, since a misconfigured third party can return 200 with a malformed payload.

Timeouts, retries, and caching

Handle a transient failure with a bounded retry and backoff, giving the third party time to recover without hammering it. If a fresh, validated answer is already cached, skip the network call entirely.

  • Check cache first.
  • Add the credential only for a real network call.
  • Validate the response before caching it.
  • Retry only transient failures, never a validation failure.
text
THIRD-PARTY INTEGRATION PIPELINE
--------------------------------
Frontend
  |
  | request (e.g. GET /exchange-rate)
  v
Backend:
  1. Check cache -- hit --> skip straight to step 6
  2. Attach secret credential (value never logged)
  3. Call third-party API
  4. Validate response -- invalid --> return error, do not cache
  5. Transient failure? -- yes --> backoff, retry (max 3 attempts)
                        -- no  --> continue
  6. Store result in cache, return clean result
  |
  v
Frontend (gets clean result, secret never exposed)

Connect it to a real scenario

Check the cache

Compute a cache key from the request and check an in-memory cache with a TTL -- a fresh hit returns immediately.

Attach the credential

Only on a cache miss, attach the credential to a copy of the request -- never let the secret's value reach a log.

Call with a bounded retry

Retry up to three attempts, doubling the backoff delay after each transient failure.

Validate the response

Run a non-transient response through validateResponse -- an invalid one returns an error immediately and is never cached.

Store and return

Only a response that succeeds and validates gets written to the cache and returned.

Read the log array

Reading each scenario's log shows exactly which stage ran and why -- the same way you would debug this in production.

Try the working example

javascript
// A deterministic stand-in for setTimeout, so backoff delays are
// simulated (logged) instead of actually pausing the test run.
function fakeDelay(ms, log) {
  log.push(`  waited ${ms}ms (backoff)`);
}

const cache = new Map();
const CACHE_TTL_MS = 60000;

// Never log the secret itself -- only that a credential was attached.
function addCredential(request, secretStore) {
  const secret = secretStore.THIRD_PARTY_KEY;
  return { ...request, headers: { ...request.headers, Authorization: `Bearer ${secret}` } };
}

function validateResponse(res) {
  if (typeof res.status !== 'number') return { valid: false, reason: 'missing_status' };
  if (res.status >= 200 && res.status < 300) {
    if (!res.body || typeof res.body.total !== 'number') {
      return { valid: false, reason: 'malformed_body' };
    }
    return { valid: true };
  }
  return { valid: false, reason: `bad_status_${res.status}` };
}

function fetchWithReliability(request, mockApiCall, secretStore, now = Date.now()) {
  const log = [];
  const cacheKey = `${request.method} ${request.path}`;

  const cached = cache.get(cacheKey);
  if (cached && now - cached.storedAt < CACHE_TTL_MS) {
    log.push('cache hit, skipping network call');
    return { ok: true, source: 'cache', data: cached.data, log };
  }
  log.push('cache miss');

  const signedRequest = addCredential(request, secretStore);
  log.push('attached credential (value not logged)');

  const maxAttempts = 3;
  let lastError = null;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    log.push(`attempt ${attempt}: calling third-party API`);
    const res = mockApiCall(signedRequest, attempt);

    if (res.transientError) {
      lastError = 'transient_error';
      log.push(`attempt ${attempt} failed with a transient error`);
      if (attempt < maxAttempts) {
        fakeDelay(2 ** attempt * 100, log);
        continue;
      }
      break;
    }

    const check = validateResponse(res);
    if (!check.valid) {
      log.push(`attempt ${attempt} returned an invalid response: ${check.reason}`);
      return { ok: false, source: 'network', error: check.reason, log };
    }

    cache.set(cacheKey, { data: res.body, storedAt: now });
    log.push('response validated and cached');
    return { ok: true, source: 'network', data: res.body, log };
  }

  return { ok: false, source: 'network', error: lastError, log };
}

// --- Fixed, deterministic scenarios -------------------------------------
const secretStore = { THIRD_PARTY_KEY: 'sk_live_should_never_be_logged' };
const request = { method: 'GET', path: '/exchange-rate', headers: {} };

console.log('Scenario 1: first-time call succeeds immediately');
let callCount = 0;
const alwaysSucceeds = () => {
  callCount++;
  return { status: 200, body: { total: 1 } };
};
console.log(fetchWithReliability(request, alwaysSucceeds, secretStore, 1000));

console.log('\nScenario 2: cache hit (same request, 5 seconds later)');
console.log(fetchWithReliability(request, alwaysSucceeds, secretStore, 6000));

console.log('\nScenario 3: transient failure on attempt 1, succeeds on attempt 2');
const differentRequest = { method: 'GET', path: '/inventory-count', headers: {} };
const flakyOnce = (req, attempt) => {
  if (attempt === 1) return { transientError: true };
  return { status: 200, body: { total: 42 } };
};
console.log(fetchWithReliability(differentRequest, flakyOnce, secretStore, 100000));
You should see
Scenario 1 (first-time success) returns { ok: true, source: 'network', data: { total: 1 } } with a log showing cache miss, credential attached, attempt 1, and 'response validated and cached'. Scenario 2 (repeat request) returns { ok: true, source: 'cache', data: { total: 1 } } with only 'cache hit, skipping network call' in the log -- no credential is attached and no network call happens. Scenario 3 (transient failure then success) returns { ok: true, source: 'network', data: { total: 42 } } with a log showing attempt 1 failing, a 200ms backoff wait, then attempt 2 succeeding.

5-minute try-it

Add a maxAttempts parameter and a scenario where every attempt fails with a transient error -- confirm fetchWithReliability gives up after 3 attempts and returns { ok: false, error: 'transient_error' } instead of retrying forever.

One important caution

Caching a response before validating it -- a malformed or error payload gets served from cache to every subsequent request until the TTL expires.

Retrying on every kind of failure, including validation failures -- retrying bad data wastes attempts and time without ever producing a different result.

AWS Architecture Blog: Exponential Backoff and JitterAPI Integration & Webhooks

Easy traps

  • Caching a response before validating it -- a malformed or error payload gets served from cache to every subsequent request until the TTL expires.
  • Retrying on every kind of failure, including validation failures -- retrying bad data wastes attempts and time without ever producing a different result.
  • If you haven't taken the API Tutorial yet, it's worth finishing that first -- this course doesn't re-teach REST/HTTP/auth basics, it builds on top of them with webhooks, testing, reliability, and integration architecture.

Exercise

Add a maxAttempts parameter and a scenario where every attempt fails with a transient error -- confirm fetchWithReliability gives up after 3 attempts and returns { ok: false, error: 'transient_error' } instead of retrying forever.

You'll know it worked when: Scenario 1 (first-time success) returns { ok: true, source: 'network', data: { total: 1 } } with a log showing cache miss, credential attached, attempt 1, and 'response validated and cached'. Scenario 2 (repeat request) returns { ok: true, source: 'cache', data: { total: 1 } } with only 'cache hit, skipping network call' in the log -- no credential is attached and no network call happens. Scenario 3 (transient failure then success) returns { ok: true, source: 'network', data: { total: 42 } } with a log showing attempt 1 failing, a 200ms backoff wait, then attempt 2 succeeding.

Project: A Complete Third-Party Integration | Thuta Learning