Start a workflow. We handle the rest. Pre-built workflows recover payments, convert trials, and retain subscribers.
Call startWorkflow() with an intent
Pre-built workflows match your events
Right message, right channel, right time
Payments saved, trials converted, subscribers retained
8 intelligent workflows, each optimized for subscription revenue recovery and growth.
Multi-step recovery adapts to failure reason and customer value
PAYMENT_FAILEDConvert based on usage depth and activation score
TRIAL_ENDINGWin back expired trials with targeted offers
TRIAL_EXPIREDRecover abandoned checkouts
CHECKOUT_STARTEDInstall the SDK and start recovering revenue in minutes.
1. Install the SDK
npm install @fourbyfour/sdk2. Initialize with your API key
import { fbf } from '@fourbyfour/sdk'; const client = fbf({ apiKey: process.env.FOURBYFOUR_API_KEY!, projectId: process.env.FOURBYFOUR_PROJECT_ID!,});Get your API key from the dashboard. 8 pre-built workflows across 4 intent categories are ready to use.
Two functions. Intent-based workflows.
Select an intent
// Payment failed → triggers recovery workflowawait client.startWorkflow({ userId: 'cus_9s6XGrhLkFm2pQ', intent: 'PAYMENT_FAILED', signals: { amountCents: 9900, reason: 'card_expired', currency: 'USD', },});// When payment is recoveredawait client.resolveConversion({ userId: 'cus_9s6XGrhLkFm2pQ', amount: 99.00,});When you call startWorkflow({ intent: 'PAYMENT_FAILED' }), our Payment Recovery workflow activates. We optimize timing, channel selection, and messaging based on the signals you provide and patterns across thousands of subscription businesses.
Answer a few questions about your stack and we'll generate a step-by-step integration guide that you can feed to your AI coding assistant (Claude Code, Cursor, Copilot, etc.).
We'll generate code examples in your language.
YOUR_PROJECT_ID and YOUR_API_KEY with your actual values from the dashboardTwo functions. Intent-based. Fully typed.
client.startWorkflow()Trigger workflows with typed intents
// Start a workflow with intent and signalsawait client.startWorkflow({ userId: 'cus_9s6XGrhLkFm2pQ', intent: 'PAYMENT_FAILED', signals: { amountCents: 9900, reason: 'card_expired', retryCount: 1, },});client.resolveConversion()Record when revenue is recovered
// Record successful conversion (payment recovered)await client.resolveConversion({ userId: 'cus_9s6XGrhLkFm2pQ', amount: 99.00,});TRIAL_ENDING, TRIAL_EXPIRED
PLAN_SELECTED, CHECKOUT_STARTED
PAYMENT_FAILED
SUBSCRIPTION_ENDING, SUBSCRIPTION_CANCELLED, SUBSCRIPTION_DOWNGRADED
Each intent has specific signal fields. TypeScript/Python type hints guide you:
// PAYMENT_FAILED signalssignals: { amountCents: number; // Required reason: 'insufficient_funds' | 'card_expired' | 'declined' | 'error'; currency?: string; // Optional retryCount?: number; plan?: { id: string; name: string }; tenureMonths?: number; ltvCents?: number;}See the Intents & Signals section for all available signals per intent.
8 intents across 4 categories. Each with typed signals.
Payment failure handling
Recover failed payments with smart retries
| Name | Type | Req | Description |
|---|---|---|---|
amountCents | number | Yes | Failed amount in cents |
reason | 'insufficient_funds' | 'card_expired' | 'declined' | 'error' | Yes | Failure reason |
currency | string | No | Currency code |
retryCount | number | No | How many retries |
plan | { id: string; name: string } | No | Their current plan |
tenureMonths | number | No | Months as customer |
ltvCents | number | No | Lifetime value in cents |
Many signals accept a Plan object:
{
id: string; // Your plan ID
name: string; // Display name
priceCents?: number; // Price in cents
tier?: string; // "starter" | "pro" | "enterprise"
}Just-in-time PII. Your data stays in your system until the moment of delivery.
User event matches a workflow
Request only needed fields
Email, phone, name: what's needed
Used for send, never stored
We send:
POST /api/fourbyfour/resolveHeaders: x-fbf-signature: sha256=... Body:{ "userId": "cus_9s6XGrhLkFm2pQ", "fields": ["email", "name"], "workflowId": "payment-recovery", "channel": "email"}You respond:
HTTP 200 OK { "email": "user@example.com", "name": "John"} // Or if user not found:HTTP 404 Not Found{ "error": "User not found"}// Express / Next.js API routeapp.post('/api/fourbyfour/resolve', async(req, res) => { // Verify the request is from Fourbyfour const signature = req.headers['x-fbf-signature']; if(!verifySignature(signature, req.body, process.env.FBF_WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }); } const { userId, fields, workflowId, channel } = req.body; // Fetch user from your database const user = await db.users.findById(userId); if(!user) { return res.status(404).json({ error: 'User not found' }); } // Return only requested fields const response: Record<string, string> = {}; if(fields.includes('email')) response.email = user.email; if(fields.includes('name')) response.name = user.firstName; if(fields.includes('phone')) response.phone = user.phone; res.json(response);});email+ namephone+ namephone+ namepushToken+ name, device| Channel | Required | Optional |
|---|---|---|
email | name | |
| SMS | phone | name |
phone | name | |
| Push | pushToken | name, device |
8 subscription revenue workflows. Track the event, we handle timing, channels, and intelligent messaging.
Start workflow
Call startWorkflow()
Workflow triggers
Matches your intent
AI optimizes
Timing, tone, offers
Revenue recovered
Payments, trials, churn
We deliver via the optimal channel. You just provide the resolver endpoint. We handle the rest.
Best for: detailed info, rich content
email
Best for: urgent, time-sensitive
phone
Best for: conversational, global
phone
Best for: real-time, mobile
pushToken
If you sent preferredChannel in signals, we start there.
We consider timezone, urgency, past engagement, and message type.
We know "users in this segment convert 2x better via WhatsApp".
User's timezone is IST
Past engagement higher on SMS
User received message yesterday
Payment fails in 24h
| Signal | Example | Impact |
|---|---|---|
| Timing | User's timezone is IST | Send at 10am local time |
| Channel | Past engagement higher on SMS | Use SMS over email |
| Frequency | User received message yesterday | Wait before next touch |
| Urgency | Payment fails in 24h | Escalate to SMS |
If a channel fails or isn't available, we automatically try the next best option: