PayWithFlash Integration
Readstr uses PayWithFlash for Bitcoin Lightning subscription payments.
Setup
1. Create a Subscription Plan on Flash
- Go to Flash Dashboard
- Navigate to New Subs > Create a Subscription Plan
- Configure your plan:
- Name: Readstr Reader
- Price: 1750 sats
- Billing Period: Monthly
- Trial Period: 7 days
2. Configure Webhook
- Click “Use Advanced Webhook Features” checkbox
- Set Webhook URL:
https://readstr.privkey.io/api/webhooks/flash - Save and copy your Subscription Key (you’ll need this for JWT verification)
- Copy your Checkout Page URL
3. Set Environment Variables
Add to your .env.production file:
# Flash Configuration
NEXT_PUBLIC_FLASH_CHECKOUT_URL="https://app.paywithflash.com/subscription-page?flashId=3238"
FLASH_SUBSCRIPTION_KEY="97ee08713791860b5c849941dd596d0208928ab7ae9a468cec993504b06daca4"
Note: These values are already configured in the production environment.
How It Works
Payment Flow
- User subscribes: User clicks “Subscribe” button → tRPC creates checkout URL with pre-filled npub
- Pre-filled form: Flash checkout page auto-fills user’s Nostr npub (no manual entry needed)
- Payment completed: Flash sends webhook to
/api/webhooks/flash - Webhook verified: JWT token is verified using
FLASH_SUBSCRIPTION_KEY - Subscription activated: User record created/updated in database with 30-day access
Pre-Filled User Details
When authenticated users click subscribe, their Nostr npub is automatically passed to Flash using Base64-encoded JSON:
{
"npub": "npub1...",
"external_uuid": "npub1...", // Same as npub for user mapping
"is_verified": true // Skip verification (already logged in)
}
This saves users from manually entering their npub and ensures the webhook links to the correct account.
Webhook Events
Flash sends the following events:
user_signed_up: New subscription created → Status: ACTIVErenewal_successful: Monthly renewal succeeded → Extend 30 daysrenewal_failed: Payment failed → Status: PAST_DUEuser_paused_subscription: User paused → Status: CANCELLEDuser_cancelled_subscription: User cancelled → Status: CANCELLED
Security
- All webhooks include a JWT token in the
Authorizationheader - Token is verified using HS256 algorithm with your subscription key
- Tokens expire after 1 hour
- Invalid tokens are rejected with 401 status
Testing
Test Webhook Locally
-
Use ngrok to expose your local server:
ngrok http 3000 -
Update Flash webhook URL to:
https://your-ngrok-url.ngrok.io/api/webhooks/flash -
Make a test payment on Flash checkout page
-
Check your server logs for webhook processing
Manual Webhook Testing
# Get a valid JWT token from Flash (check their test tools)
curl -X POST https://readstr.privkey.io/api/webhooks/flash \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"eventType": {"id": "1", "name": "user_signed_up"},
"data": {
"npub": "npub1test...",
"email": "test@example.com"
}
}'
Database Schema
Subscriptions are stored in the UserSubscription table:
model UserSubscription {
userPubkey String @unique // Nostr npub or external_uuid
status SubscriptionStatus
trialEndsAt DateTime
subscriptionEndsAt DateTime?
cancelledAt DateTime?
...
}
Subscription Statuses
TRIAL: In 7-day free trial (not used with Flash, goes directly to ACTIVE)ACTIVE: Paid and active subscriptionPAST_DUE: Payment failed, grace periodCANCELLED: User cancelled, valid until end dateEXPIRED: Subscription expired
Troubleshooting
Webhooks not received
- Check Flash dashboard for webhook delivery logs
- Verify webhook URL is accessible publicly (use curl)
- Check server logs for errors
- Ensure HTTPS is enabled (Flash requires HTTPS)
JWT verification fails
- Verify
FLASH_SUBSCRIPTION_KEYmatches the key in Flash dashboard - Check token hasn’t expired (1 hour validity)
- Ensure secret key has no extra whitespace
User not getting access
- Check if webhook was received (server logs)
- Verify user identifier (npub/external_uuid) is correct
- Check database for UserSubscription record
- Verify subscription status is ACTIVE