Bundle Integration Guide: RadioCheck + Last1¶
Architecture Overview¶
┌─────────────────────────────────────────────────────────────┐
│ RADIOCHECK SUPABASE │
│ (Single source for RadioCheck + bundle billing) │
├─────────────────────────────────────────────────────────────┤
│ Tables: │
│ • users, organizations, check_ins, units │
│ • last1_organizations (mirror/cache of Last1 orgs) │
│ • bundle_pricing, bundle_subscription_log │
│ │
│ Connects to: │
│ • RadioCheck Mobile App (this codebase) │
│ • radiocheck.app (Lovable web frontend) │
└─────────────────────────────────────────────────────────────┘
│
(Webhook Sync)
│
▼
┌─────────────────────────────────────────────────────────────┐
│ LAST1 SUPABASE (Separate) │
├─────────────────────────────────────────────────────────────┤
│ Tables (no prefix): │
│ • organizations, beneficiaries, programs │
│ • users, check_ins, outcomes, funding │
│ │
│ Connects to: │
│ • last1.app │
└─────────────────────────────────────────────────────────────┘
Integration Approach: Webhook Sync¶
Since RadioCheck and Last1 have separate Supabase instances, bundle status must be synchronized via webhooks.
Data Flow: Bundle Purchase¶
1. Org admin on radiocheck.app clicks "Upgrade to VSO Bundle"
2. RadioCheck creates Stripe checkout with bundle metadata
3. User completes payment on Stripe
4. Stripe webhook → RadioCheck Edge Function (stripe-webhook)
5. RadioCheck updates:
- organizations.bundle_type = 'vso_pro'
- organizations.linked_last1_org_id = '<uuid>'
- last1_organizations.bundle_type = 'vso_pro' (local mirror)
6. RadioCheck calls Last1 webhook endpoint:
POST https://last1.app/api/webhooks/bundle
{
"event": "bundle.activated",
"bundle_type": "vso_pro",
"last1_org_id": "<uuid>",
"radiocheck_org_id": "<uuid>",
"plan": "pro",
"activated_at": "2026-01-29T..."
}
7. Last1 receives webhook and updates its own database:
- organizations.plan = 'pro'
- organizations.bundle_type = 'vso_pro'
- organizations.bundle_managed_by = 'radiocheck'
Webhook Endpoints Needed¶
RadioCheck → Last1 (outbound from RadioCheck)¶
Endpoint: POST https://last1.app/api/webhooks/bundle
Events:
- bundle.activated - Bundle subscription started
- bundle.upgraded - Bundle tier changed (e.g., vso_starter → vso_pro)
- bundle.downgraded - Bundle tier decreased
- bundle.cancelled - Bundle subscription ended
- bundle.payment_failed - Payment issue (grace period)
Payload:
{
"event": "bundle.activated",
"timestamp": "2026-01-29T12:00:00Z",
"bundle_type": "vso_pro",
"radiocheck_org_id": "uuid",
"last1_org_id": "uuid",
"last1_plan": "pro",
"stripe_subscription_id": "sub_xxx",
"signature": "sha256_hmac_signature"
}
Last1 → RadioCheck (optional, for org linking)¶
Endpoint: POST https://radiocheck.app/api/webhooks/last1
Events:
- org.linked - Last1 org wants to link with RadioCheck org
- org.unlinked - Last1 org unlinks from RadioCheck
Pricing Matrix¶
Standalone Pricing¶
| Platform | Tier | Price |
|---|---|---|
| RadioCheck | Starter | $99/mo |
| RadioCheck | Pro | $299/mo |
| RadioCheck | Impact | $799/mo |
| RadioCheck | Enterprise | Custom |
| Last1 | Free | $0/mo |
| Last1 | Pro | $199/mo |
| Last1 | Enterprise | Custom |
Bundle Pricing (10% off combined)¶
| Bundle | RC Tier | L1 Tier | Regular | Bundled | Savings |
|---|---|---|---|---|---|
| VSO Starter | Starter | Pro | $298 | $268/mo | $30/mo |
| VSO Pro | Pro | Pro | $498 | $448/mo | $50/mo |
| VSO Impact | Impact | Pro | $998 | $898/mo | $100/mo |
| VSO Enterprise | Custom | Custom | Custom | Custom | Custom |
Implementation Checklist¶
RadioCheck Side (this codebase) ✅ DONE¶
- [x] Database migration
003_bundle_pricing.sql - [x] Bundle configuration in
constants/tiers.ts - [x] Billing hook with bundle functions
hooks/useBilling.ts - [x] Stripe webhook handling bundle events
- [x] Stripe checkout for bundles
- [x] Organization settings UI with bundle options
- [x] PricingTable component
RadioCheck Side - Webhook Outbound (TODO)¶
- [ ] Create Edge Function
supabase/functions/sync-last1/index.ts - [ ] Add webhook call after bundle activation in stripe-webhook
- [ ] Store Last1 webhook secret in env vars
- [ ] Add retry logic for failed webhook deliveries
Last1 Side¶
- [ ] Create webhook endpoint
/api/webhooks/bundle - [ ] Verify webhook signatures
- [ ] Update organizations table with bundle status
- [ ] Show "Bundled via RadioCheck" UI when bundled
- [ ] Disable independent plan changes when bundled
- [ ] Link to RadioCheck for billing management
Prompts¶
RadioCheck.app Prompt¶
UPDATE: Add bundle webhook outbound calls
After implementing bundle pricing, we need to sync bundle status to Last1.app.
When a bundle is activated/changed/cancelled via Stripe webhook:
1. Update local RadioCheck database (already done)
2. Send webhook to Last1.app to sync their database
Create a new Edge Function: supabase/functions/sync-last1/index.ts
It should:
- Accept bundle event data (event type, bundle_type, org IDs, plan)
- Sign the payload with LAST1_WEBHOOK_SECRET
- POST to https://last1.app/api/webhooks/bundle
- Log success/failure
- Support retry with exponential backoff
Update stripe-webhook to call this function after bundle events.
Environment variables needed:
- LAST1_WEBHOOK_SECRET (shared secret for HMAC signature)
- LAST1_WEBHOOK_URL (https://last1.app/api/webhooks/bundle)
Last1.app Prompt¶
UPDATE: Implement RadioCheck Bundle Webhook Receiver
We've integrated bundle pricing with RadioCheck. VSO organizations can purchase
bundles on radiocheck.app that include Last1 Pro access.
Create webhook endpoint: POST /api/webhooks/bundle
Expected payload:
{
"event": "bundle.activated" | "bundle.upgraded" | "bundle.downgraded" | "bundle.cancelled",
"timestamp": "ISO8601",
"bundle_type": "vso_starter" | "vso_pro" | "vso_impact" | "vso_enterprise",
"radiocheck_org_id": "uuid",
"last1_org_id": "uuid",
"last1_plan": "free" | "pro" | "enterprise",
"stripe_subscription_id": "sub_xxx",
"signature": "sha256_hmac"
}
Implementation:
1. Verify HMAC signature using RADIOCHECK_WEBHOOK_SECRET env var
2. Find organization by last1_org_id
3. Update organization record:
- plan = payload.last1_plan
- bundle_type = payload.bundle_type (add this column if missing)
- bundle_managed_by = 'radiocheck' (add this column if missing)
- radiocheck_org_id = payload.radiocheck_org_id
4. Return 200 OK
Database migration needed:
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS bundle_type TEXT;
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS bundle_managed_by TEXT;
ALTER TABLE organizations ADD COLUMN IF NOT EXISTS radiocheck_org_id UUID;
UI Changes:
1. On billing/settings page, check if bundle_managed_by = 'radiocheck'
2. If bundled, show:
- "Your Last1 Pro plan is included in your VSO Bundle"
- "Manage billing at radiocheck.app"
- Hide upgrade/downgrade buttons
- Link to RadioCheck billing portal
Environment variables:
- RADIOCHECK_WEBHOOK_SECRET (shared secret, must match RadioCheck's LAST1_WEBHOOK_SECRET)
Security Considerations¶
- Webhook Signatures: All webhooks must be HMAC-SHA256 signed
- Shared Secret: Generate a strong random secret shared between platforms
- Idempotency: Webhook handlers must be idempotent (safe to retry)
- Logging: Log all webhook events for debugging
- Rate Limiting: Implement rate limiting on webhook endpoints
Testing Checklist¶
- [ ] Create bundle subscription on RadioCheck
- [ ] Verify RadioCheck database updated
- [ ] Verify webhook sent to Last1
- [ ] Verify Last1 database updated
- [ ] Verify Last1 UI shows "bundled" status
- [ ] Cancel bundle on RadioCheck
- [ ] Verify both databases downgraded
- [ ] Test webhook retry on failure