Fourbyfour
WorkflowsDocs
PricingWorkflowsDocsAboutBlogCareerSupportDiscordPrivacyTerms

© 2026 Occupy Mars Private Limited. All rights reserved.

Getting Started

SDK

Platform

DocsIntroduction

Recover & Grow Subscription Revenue

Start a workflow. We handle the rest. Pre-built workflows recover payments, convert trials, and retain subscribers.

How it works

1

Start workflow

Call startWorkflow() with an intent

2

Workflows trigger

Pre-built workflows match your events

3

Users notified

Right message, right channel, right time

4

Revenue recovered

Payments saved, trials converted, subscribers retained

Subscription Workflows

8 intelligent workflows, each optimized for subscription revenue recovery and growth.

Payment Recovery

Multi-step recovery adapts to failure reason and customer value

PAYMENT_FAILED

Trial Conversion

Convert based on usage depth and activation score

TRIAL_ENDING

Failed Trial Recovery

Win back expired trials with targeted offers

TRIAL_EXPIRED

Checkout Recovery

Recover abandoned checkouts

CHECKOUT_STARTED
View all 8 workflows

Installation

Install the SDK and start recovering revenue in minutes.

1. Install the SDK

1npm install @fourbyfour/sdk
TypeScript
Ln 1UTF-8

2. Initialize with your API key

123456import { fbf } from '@fourbyfour/sdk'; const client = fbf({  apiKey: process.env.FOURBYFOUR_API_KEY!,  projectId: process.env.FOURBYFOUR_PROJECT_ID!,});
TypeScript
Ln 6UTF-8

Get your API key from the dashboard. 8 pre-built workflows across 4 intent categories are ready to use.

Quick Start

Two functions. Intent-based workflows.

Select an intent

client.startWorkflow()

→ triggers Payment Recovery
12345678910// Payment failed → triggers recovery workflowawait client.startWorkflow({  userId: 'cus_9s6XGrhLkFm2pQ',  intent: 'PAYMENT_FAILED',  signals: {    amountCents: 9900,    reason: 'card_expired',    currency: 'USD',  },});
TypeScript
Ln 10UTF-8

client.resolveConversion()

→ records success
12345// When payment is recoveredawait client.resolveConversion({  userId: 'cus_9s6XGrhLkFm2pQ',  amount: 99.00,});
TypeScript
Ln 5UTF-8

What happens next?

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.

AI Integration Generator

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.).

What's your tech stack?

We'll generate code examples in your language.

How to use the generated guide:

  1. Complete the wizard above to generate your integration guide
  2. Copy the guide or download as a markdown file
  3. Open your AI coding assistant (Claude Code, Cursor, etc.)
  4. Paste the guide and ask it to "implement this integration"
  5. Replace YOUR_PROJECT_ID and YOUR_API_KEY with your actual values from the dashboard
  6. Review the changes and commit

Two Functions

Two functions. Intent-based. Fully typed.

client.startWorkflow()

Trigger workflows with typed intents

12345678910// Start a workflow with intent and signalsawait client.startWorkflow({  userId: 'cus_9s6XGrhLkFm2pQ',  intent: 'PAYMENT_FAILED',  signals: {    amountCents: 9900,    reason: 'card_expired',    retryCount: 1,  },});
TypeScript
Ln 10UTF-8
client.resolveConversion()

Record when revenue is recovered

12345// Record successful conversion (payment recovered)await client.resolveConversion({  userId: 'cus_9s6XGrhLkFm2pQ',  amount: 99.00,});
TypeScript
Ln 5UTF-8

4 Intent Categories

CONVERSION

TRIAL_ENDING, TRIAL_EXPIRED

EXPANSION

PLAN_SELECTED, CHECKOUT_STARTED

RECOVERY

PAYMENT_FAILED

CHURN

SUBSCRIPTION_ENDING, SUBSCRIPTION_CANCELLED, SUBSCRIPTION_DOWNGRADED

Typed Signals

Each intent has specific signal fields. TypeScript/Python type hints guide you:

12345678910// 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;}
TypeScript
Ln 10UTF-8

See the Intents & Signals section for all available signals per intent.

Intents & Signals

8 intents across 4 categories. Each with typed signals.

Payment failure handling

Recover failed payments with smart retries

Signals

NameTypeReqDescription
amountCentsnumberYesFailed amount in cents
reason'insufficient_funds' | 'card_expired' | 'declined' | 'error'YesFailure reason
currencystringNoCurrency code
retryCountnumberNoHow many retries
plan{ id: string; name: string }NoTheir current plan
tenureMonthsnumberNoMonths as customer
ltvCentsnumberNoLifetime value in cents

Plan Type

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"
}

Resolver

Just-in-time PII. Your data stays in your system until the moment of delivery.

How It Works

1

Workflow triggers

User event matches a workflow

2

We call your resolver

Request only needed fields

3

You return PII

Email, phone, name: what's needed

4

We deliver & discard

Used for send, never stored

Request & Response

We send:

{}JSON
1234567891011POST /api/fourbyfour/resolveHeaders:  x-fbf-signature: sha256=... Body:{  "userId": "cus_9s6XGrhLkFm2pQ",  "fields": ["email", "name"],  "workflowId": "payment-recovery",  "channel": "email"}
JSON
Ln 11UTF-8

You respond:

{}JSON
123456789101112HTTP 200 OK {  "email": "user@example.com",  "name": "John"} // Or if user not found:HTTP 404 Not Found{  "error": "User not found"}
JSON
Ln 12UTF-8

Implementation

12345678910111213141516171819202122232425// 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);});
TypeScript
Ln 25UTF-8

Fields by Channel

Email
email+ name
SMS
phone+ name
WhatsApp
phone+ name
Push
pushToken+ name, device
ChannelRequiredOptional
Emailemailname
SMSphonename
WhatsAppphonename
PushpushTokenname, device

Why This Architecture

Privacy by Design

  • • PII never leaves your system until needed
  • • We request only what the channel requires
  • • Data used for delivery, then discarded

Always Fresh

  • • No stale emails or phones
  • • User updates their info? Next send has it
  • • User deletes account? Returns 404, we skip

You Stay in Control

  • • Return only what you want to share
  • • Add business logic (block certain users)
  • • GDPR compliant by default

Workflows

8 subscription revenue workflows. Track the event, we handle timing, channels, and intelligent messaging.

How it works

1

Start workflow

Call startWorkflow()

2

Workflow triggers

Matches your intent

3

AI optimizes

Timing, tone, offers

4

Revenue recovered

Payments, trials, churn

Try all workflows in the playground

Channels

We deliver via the optimal channel. You just provide the resolver endpoint. We handle the rest.

Email

Best for: detailed info, rich content

email

SMS

Best for: urgent, time-sensitive

phone

WhatsApp

Best for: conversational, global

phone

Push

Best for: real-time, mobile

pushToken

How We Select Channels

1

User Preference

If you sent preferredChannel in signals, we start there.

2

Context Analysis

We consider timezone, urgency, past engagement, and message type.

3

Cross-org Learning

We know "users in this segment convert 2x better via WhatsApp".

What We Optimize

Timing→ Send at 10am local

User's timezone is IST

Channel→ Use SMS over email

Past engagement higher on SMS

Frequency→ Wait before next

User received message yesterday

Urgency→ Escalate to SMS

Payment fails in 24h

SignalExampleImpact
TimingUser's timezone is ISTSend at 10am local time
ChannelPast engagement higher on SMSUse SMS over email
FrequencyUser received message yesterdayWait before next touch
UrgencyPayment fails in 24hEscalate to SMS

Automatic Fallbacks

If a channel fails or isn't available, we automatically try the next best option:

Email fails→Try SMS→Try Push