Skip to main content
Preview
Preview Feature — This feature is currently in preview and under active development. APIs and functionality may change. We recommend testing thoroughly before using in production.

Fallbacks

Automatically failover to backup providers when your primary provider fails.

Overview​

Fallbacks provide sequential failover - try the primary provider first, and if it fails, automatically try the next one in line. This ensures your operations complete even when individual providers have issues.

Defining a Fallback​

const fallback = await resilience.fallback.define({
tag: 'payment-fallback',
name: 'Payment Provider Fallback',
description: 'Failover between payment providers',
input: {
amount: { type: 'number' },
currency: { type: 'string' },
card_token: { type: 'string' },
},
handler: async (ctx) => {
// Primary provider - tried first
ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);

// Fallback provider - tried if primary fails
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
intent: 'CAPTURE',
}
}))
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
}))
.retries(2);
},
});

Primary vs Fallback​

Primary Provider​

The first provider to try. Define with ctx.primary():

ctx.primary('stripe')
.healthcheck('stripe-health')
.app('stripe-app')
.action('charge');

Fallback Providers​

Backup providers tried in order if primary fails. Define with ctx.fallback():

// First fallback
ctx.fallback('paypal')
.healthcheck('paypal-health')
.app('paypal-app')
.action('payment');

// Second fallback
ctx.fallback('square')
.healthcheck('square-health')
.app('square-app')
.action('create-payment');

Provider Configuration​

Healthcheck​

Link a healthcheck to skip unhealthy providers:

ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');

Target Types​

App Action​

ctx.primary('stripe')
.app('stripe-app')
.action('charge');

Database​

ctx.primary('primary-db')
.database('main-db')
.action('write');

Feature​

ctx.primary('standard-flow')
.feature('order-processing')
.input({ priority: 'high' });

Notification​

ctx.primary('sendgrid')
.notification('transactional-email')
.event('send');

Input/Output Mapping​

Transform input/output for each provider:

ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.mapInput((input) => ({
body: {
amount: input.amount,
currency: input.currency,
source: input.card_token,
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status,
provider: 'stripe',
}));

ctx.fallback('paypal')
.app('paypal-app')
.action('payment')
.mapInput((input) => ({
body: {
amount: { value: input.amount / 100, currency: input.currency },
}
}))
.mapOutput((response) => ({
chargeId: response.id,
status: response.status === 'COMPLETED' ? 'succeeded' : response.status,
provider: 'paypal',
}));

Retries​

Configure retries per provider:

ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.retries(3); // Retry 3 times before trying fallback

Check Interval​

Time between health checks:

ctx.primary('stripe')
.app('stripe-app')
.action('charge')
.checkInterval(10000); // Check health every 10 seconds

Running Fallbacks​

Synchronous Run​

const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: {
amount: 5000,
currency: 'usd',
card_token: 'tok_visa',
},
});

With Session​

const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
session: {
tag: 'checkout-session',
token: 'session-123'
},
});

With Caching​

const result = await resilience.fallback.run({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
cache: 'payment-cache',
});

Dispatching Fallbacks​

Schedule fallback execution for later:

// Dispatch with delay
const job = await resilience.fallback.dispatch({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
schedule: {
delay: 5000, // Run after 5 seconds
},
});

// Dispatch at specific time
const job = await resilience.fallback.dispatch({
tag: 'payment-fallback',
input: { amount: 5000, currency: 'usd', card_token: 'tok_visa' },
schedule: {
at: new Date('2024-12-25T00:00:00Z'),
},
});

Managing Fallbacks​

Create​

await resilience.fallback.create(productId, fallback);

Fetch​

const fb = await resilience.fallback.fetch(productId, 'payment-fallback');

Fetch All​

const fallbacks = await resilience.fallback.fetchAll(productId);

Update​

await resilience.fallback.update(productId, 'payment-fallback', {
input: { amount: { type: 'number' }, currency: { type: 'string' } },
handler: async (ctx) => {
ctx.primary('stripe').app('stripe-app').action('charge');
ctx.fallback('paypal').app('paypal-app').action('payment');
ctx.fallback('square').app('square-app').action('create-payment');
},
});

Delete​

await resilience.fallback.delete(productId, 'payment-fallback');

How Fallback Selection Works​

  1. Check primary health: Is the primary provider healthy?
  2. Try primary: If healthy, execute the primary provider
  3. On failure: If primary fails (after retries), move to next fallback
  4. Check fallback health: Is the fallback provider healthy?
  5. Try fallback: If healthy, execute the fallback
  6. Repeat: Continue until success or no more fallbacks

Fallbacks vs Quotas​

FeatureFallbacksQuotas
StrategySequential failoverWeighted distribution
Use caseBackup providersLoad balancing
SelectionTry in orderRandom by weight
Traffic100% to primarySplit across providers

When to Use Fallbacks​

  • You have a preferred provider
  • You want guaranteed execution order
  • Cost varies significantly between providers

When to Use Quotas​

  • You want to distribute load
  • All providers are equally capable
  • You want to test new providers with limited traffic

Best Practices​

Always Use Healthchecks​

Ensure you skip unhealthy providers:

ctx.primary('stripe')
.healthcheck('stripe-health') // Skip if unhealthy
.app('stripe-app')
.action('charge');

Normalize Output​

Use mapOutput for consistent responses:

// Both return { chargeId, status, provider }
ctx.primary('stripe')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status,
provider: 'stripe'
}));

ctx.fallback('paypal')
.mapOutput((res) => ({
chargeId: res.id,
status: res.status === 'COMPLETED' ? 'succeeded' : res.status,
provider: 'paypal'
}));

Set Appropriate Retries​

Balance reliability vs latency:

// Primary: more retries (it's preferred)
ctx.primary('stripe')
.retries(3);

// Fallbacks: fewer retries (already in fallback mode)
ctx.fallback('paypal')
.retries(1);

Order Fallbacks by Preference​

Put most reliable/cost-effective fallbacks first:

ctx.primary('stripe');        // Best rates
ctx.fallback('paypal'); // Good alternative
ctx.fallback('square'); // Last resort