Track events. We handle the rest. Pre-built workflows recover payments, convert trials, and retain subscribers—optimized across thousands of subscription businesses.
Call fbf.track() when things happen
Pre-built workflows match your events
Right message, right channel, right time
Payments saved, trials converted, subscribers retained
12 intelligent workflows, each optimized for subscription revenue recovery and growth.
Proactive outreach before payment fails completely
payment.failingMulti-step recovery adapts to failure reason and customer value
payment.failedGuide to activation based on what they haven't tried yet
trial.startedConvert based on usage depth and activation score
trial.endingInstall the SDK and start recovering revenue in minutes.
1. Install the SDK
npm install @fourbyfour/sdk2. Initialize with your API key
import Fourbyfour from '@fourbyfour/sdk'; const fbf = new Fourbyfour({ apiKey: process.env.FOURBYFOUR_API_KEY, projectId: process.env.FOURBYFOUR_PROJECT_ID});Get your API key from the dashboard. 12 pre-built workflows optimized for subscription revenue recovery are ready to use.
Two functions. That's all you need.
Select an event
// Payment failed → triggers recovery workflowawait fbf.track('payment.failed', { userId: 'cus_9s6XGrhLkFm2pQ', plan: 'Pro', amount: 99, currency: 'USD', failureReason: 'card_declined'});// Send context to help optimize deliveryawait fbf.notify({ userId: 'cus_9s6XGrhLkFm2pQ', timezone: 'America/New_York', preferredChannel: 'email', tier: 'premium'});When you call track('payment.failed'), our Payment Recovery workflow kicks in automatically. We handle timing, channel selection, message optimization—all tuned from 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. No PII. That's the entire SDK.
fbf.track(event, payload)Events that trigger workflows
import Fourbyfour from '@fourbyfour/sdk'; const fbf = new Fourbyfour({ apiKey: process.env.FOURBYFOUR_API_KEY, projectId: process.env.FOURBYFOUR_PROJECT_ID}); // Minimum: just userId (we use defaults)await fbf.track('payment.failed', { userId: 'cus_9s6XGrhLkFm2pQ'}); // Full: everything you know (better optimization)await fbf.track('payment.failed', { userId: 'cus_9s6XGrhLkFm2pQ', plan: 'Pro', amount: 99, currency: 'USD', failureReason: 'card_declined'});fbf.notify(context)Context that helps us optimize
// Send context anytime to help us optimize deliveryawait fbf.notify({ userId: 'cus_9s6XGrhLkFm2pQ', timezone: 'America/New_York', // When to reach them tier: 'premium', // Priority signals preferredChannel: 'email', // Channel preference language: 'en', // Localization ltv: 1200 // Value signals});Works out of the box. We use intelligent defaults optimized across thousands of companies.
await fbf.track('payment.failed', { userId: 'cus_9s6XGrhLkFm2pQ'});Better optimization. More context = smarter timing, channels, and messaging.
await fbf.track('payment.failed', { userId: 'cus_9s6XGrhLkFm2pQ', plan: 'Pro', amount: 99, currency: 'USD', failureReason: 'card_declined'});The key: Send what you have, when you have it. No need to block on missing fields. We fill gaps with smart defaults, then improve as you send more.
trackNo PIIBusiness events: amount, plan, cartTotal
notifyNo PIIUser context: timezone, tier, preferences
resolverTransientDelivery info: email, phone, name
| Function | Data Type | Example | PII? |
|---|---|---|---|
track | Business events | amount, plan, cartTotal | No |
notify | User context | timezone, tier, preferences | No |
resolver | Delivery info | email, phone, name | Yes (transient) |
Notice: PII only flows through your resolver endpoint, fetched just-in-time, used for delivery, never stored. See the Resolver section.
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 |
12 subscription revenue workflows. Track the event, we handle timing, channels, and intelligent messaging.
Track event
Call client.track()
Workflow triggers
Matches your event
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 via notify(), 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: