Build an Insurance Premium Collection Portal
Build an insurance premium portal by creating a Paystack Plan per insurance product (health, motor, microinsurance), subscribing policyholders via the transaction initialize endpoint, setting policy status to "active" on subscription.create, applying a 14-day grace period on invoice.payment_failed before suspending coverage, and disabling the subscription on subscription.disable to mark the policy as "lapsed".
Policy Enrollment and Premium Setup
Tables: insurance_products (name, type, coverage_amount, premium_monthly, paystack_plan_code, terms_url), policies (holder_id, product_id, policy_number, paystack_subscription_code, status, start_date, grace_until, expires_at), claims (policy_id, type, amount, status, submitted_at).
function generatePolicyNumber() {
return 'POL-' + new Date().getFullYear() + '-' + Math.random().toString(36).toUpperCase().slice(2, 8);
}
async function enrollPolicyholder(holderEmail, holderId, productId) {
var product = await db.insuranceProducts.findById(productId);
var policy = await db.policies.create({
holder_id: holderId,
product_id: productId,
policy_number: generatePolicyNumber(),
status: 'pending_first_payment',
start_date: new Date(),
});
var res = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
email: holderEmail,
amount: product.premium_monthly,
plan: product.paystack_plan_code,
metadata: { holder_id: holderId, policy_id: policy.id, product_id: productId },
}),
});
return { policy, authorizationUrl: (await res.json()).data.authorization_url };
}
// Webhook: activate policy on first premium
if (event.event === 'subscription.create') {
var meta = event.data.metadata;
var nextPayment = new Date(event.data.next_payment_date);
await db.policies.update(meta.policy_id, {
paystack_subscription_code: event.data.subscription_code,
status: 'active',
expires_at: nextPayment,
});
await issuePolicyCertificate(meta.policy_id, meta.holder_id);
}
Premium Renewal, Grace Period, and Policy Lapse
// Renewal confirmed
if (event.event === 'invoice.create' && event.data.status === 'success') {
var code = event.data.subscription.subscription_code;
await db.policies.update({ code }, {
status: 'active',
grace_until: null,
expires_at: new Date(event.data.next_payment_date),
});
}
// Failed payment: enter grace period
if (event.event === 'invoice.payment_failed') {
var code = event.data.subscription.subscription_code;
var graceUntil = new Date();
graceUntil.setDate(graceUntil.getDate() + 14); // 14-day grace period
await db.policies.update({ code }, { status: 'grace_period', grace_until: graceUntil });
await sendPremiumFailedEmail(code,
'Your premium payment failed. Coverage continues until ' + graceUntil.toDateString() +
'. Please update your payment method to avoid policy lapse.'
);
}
// Policy cancelled or subscription disabled — lapse
if (event.event === 'subscription.disable') {
var code = event.data.subscription_code;
await db.policies.update({ code }, { status: 'lapsed' });
await notifyPolicyLapsed(code);
}
// Cron: suspend coverage after grace period expires
async function suspendLapsedPolicies() {
var now = new Date();
var overdue = await db.policies.findAll({ status: 'grace_period', grace_until_lt: now });
for (var policy of overdue) {
await db.policies.update(policy.id, { status: 'suspended' });
await sendCoverageSuspendedEmail(policy.holder_id);
}
}
// Claim eligibility check
async function isEligibleForClaim(policyId) {
var policy = await db.policies.findById(policyId);
return policy.status === 'active' || policy.status === 'grace_period';
}
Learn More
See build a gym membership app for the same grace period and subscription lifecycle pattern applied to a simpler membership product.
Key Takeaways
- ✓Create one Paystack Plan per insurance product — link each policy to the correct plan code.
- ✓Map subscription lifecycle events to policy status: active, grace_period, suspended, lapsed.
- ✓Apply a 14-day grace period on invoice.payment_failed before suspending coverage.
- ✓Generate a digital policy certificate on the first premium payment confirmation.
- ✓For microinsurance, consider weekly or daily Paystack Plans to match informal worker income patterns.
Frequently Asked Questions
- Do I need an insurance licence to collect premiums via Paystack?
- Yes. In Nigeria, collecting insurance premiums requires NAICOM licensing. In Kenya, you need IRA approval. Building a premium collection portal for a licensed insurer (as a technology partner, not the insurer itself) is the typical developer role. Your platform collects payments on behalf of the licensed insurer, which holds the risk and regulatory obligations.
- How should I handle a claim submission?
- Claims are separate from payments. Build a claims portal where policyholders upload supporting documents (hospital bills, police reports, photos), and a claims assessor reviews and approves or rejects. Payment of approved claims goes via Paystack Transfers to the policyholder's bank account. Track claim processing time as a key service metric.
- Can I offer microinsurance with weekly premiums instead of monthly?
- Yes. Paystack Plans support weekly billing intervals. Create a weekly_premium plan with a lower amount (e.g., NGN 250/week = NGN 1,000/month equivalent). Weekly billing matches the income patterns of informal workers who are paid daily or weekly. The subscription lifecycle webhooks work identically — only the invoice frequency changes.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon