Monetize Your Channel
Your Way
Connect Stripe, PayPal, Patreon, or any payment provider. We handle the access codes and email delivery. You keep 100% of the revenue.
No Code Required
Supported Payment Providers
Pick the payment provider you already use. We connect to it, generate access codes automatically, and email your customers their personal link.
Direct Payment — we host a checkout page for you
Stripe
Accept cards, Apple Pay, Google Pay. We create Stripe Checkout sessions with your API key.
Hosted checkoutRevolut Business
Accept payments via Revolut checkout. Fast setup, supports multiple currencies.
Hosted checkoutPayPal
Accept PayPal payments. Buyers pay with their PayPal account or credit card.
Hosted checkoutCreator Platforms — they handle payment, we handle access
Patreon
Patrons automatically get access when they pledge at your minimum tier.
Gumroad
Sell access as a Gumroad product. Buyers get their link after purchase.
LemonSqueezy
Sell access through your Lemon Squeezy store. Handles tax and payments.
Ko-fi
Supporters who buy you a "coffee" or join your membership get channel access.
Buy Me a Coffee
Supporters and members automatically receive access to your channel.
You keep 100% of the revenue. We never touch your money. Payments go directly to your account on the provider you choose. We only generate the access codes and email the link.
Set Up
Connect Your Provider
Sign in, pick your payment provider, enter your API credentials, and we give you either a payment link (for Stripe/Revolut/PayPal) or a webhook URL (for Patreon/Gumroad/etc.).
Sign in below
Use the same Apple account you use in the My TV Channel app.
Pick provider & enter keys
Select your payment provider and enter your API key or webhook secret. Set price and duration.
Share your link
Get a payment link to share with your audience, or paste the webhook URL into your provider's settings.
Connect your payment provider
Sign in with the same Apple account you use in the app.
Where to find your Stripe API key: Go to Stripe Dashboard → Developers → API keys → copy your Secret key (starts with sk_live_).
What your customers will receive
After a successful payment, your customer receives an email with their personal access link:
Your access is ready
Thanks for your purchase! You now have access to Your Channel Name.
Watch NowThis link is personal. It gives you 30 days of access.
Overview
How It Works
You handle the payment. We handle the access. When a customer pays you (through your website, Stripe, Patreon, or any other method), your server calls our API to generate a unique access code. You give that code to your customer as a link.
Customer pays you
On your website, Patreon, or any payment system you choose.
Your server calls our API
Generate a subscriber code with the expiration and limits you want.
Customer gets a link
Like https://your-channel.localtvbroadcast.com/?subscriber=ABC2DEF
Customer watches
Opens in the app (with access granted) or plays directly in the web browser.
You keep 100% of the revenue. We don't process payments or take a commission. Subscriber codes simply grant access to your channel within My TV Channel.
Getting Started
Authentication
All publisher API calls require a JWT token. Obtain one by signing in with your My TV Channel account:
# Sign in to get your JWT token
curl -X POST https://localtvbroadcast.com/api/mytvchannel/auth/login.php \
-H "Content-Type: application/json" \
-d '{
"email": "you@example.com",
"password": "your-password"
}'
The response includes a token field. Use it in all subsequent requests:
Authorization: Bearer YOUR_JWT_TOKEN
Keep your token secure. Store it as a server-side environment variable. Never expose it in client-side code or public repositories.
Base URL
All API endpoints use the following base URL:
https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/
API Reference
Generate a Code
/subscriber-codes/generate.php
Create a new subscriber access code for your channel. Requires authentication and channel ownership.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| channel_id | integer | required | Your channel's ID |
| expires_at | string | required | ISO 8601 date when the code stops accepting new redemptions |
| label | string | optional | Internal label (e.g., "VIP Monthly Pass") |
| price | number | optional | Your price (metadata only, we don't process payment) |
| currency | string | optional | ISO 4217 currency code (default: USD) |
| max_redemptions | integer | optional | Max uses. null = unlimited |
| access_duration_days | integer | optional | Days of access per redemption. null = access until expires_at |
Example
curl -X POST https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/generate.php \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 42,
"expires_at": "2026-12-31T23:59:59Z",
"label": "Monthly Pass - March",
"price": 9.99,
"currency": "USD",
"max_redemptions": 1,
"access_duration_days": 30
}'
Response
{
"success": true,
"message": "Subscriber code generated",
"data": {
"id": 1,
"code": "A3F7K2N",
"url": "https://your-channel.localtvbroadcast.com/?subscriber=A3F7K2N",
"channel_id": 42,
"label": "Monthly Pass - March",
"price": 9.99,
"currency": "USD",
"max_redemptions": 1,
"expires_at": "2026-12-31T23:59:59+00:00",
"access_duration_days": 30
}
}
Single-use vs. multi-use codes: Set max_redemptions: 1 for a unique code per customer. Set it to null for a shared promo code that anyone can use.
Access Duration Explained
Two models are supported:
| Scenario | access_duration_days | Behavior |
|---|---|---|
| Time-limited pass | 30 | Each viewer gets 30 days from the moment they redeem |
| Event pass | null | All viewers have access until the code's expires_at date |
List Codes
/subscriber-codes/list.php?channel_id={id}
Retrieve all subscriber codes for your channel, with redemption stats.
Example
curl https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/list.php?channel_id=42 \
-H "Authorization: Bearer YOUR_TOKEN"
Response
{
"success": true,
"data": {
"codes": [
{
"id": 1,
"code": "A3F7K2N",
"url": "https://your-channel.localtvbroadcast.com/?subscriber=A3F7K2N",
"label": "Monthly Pass - March",
"price": 9.99,
"currency": "USD",
"max_redemptions": 1,
"redemption_count": 0,
"expires_at": "2026-12-31T23:59:59+00:00",
"access_duration_days": 30,
"is_active": true,
"created_at": "2026-02-21 14:00:00"
}
]
}
}
Revoke a Code
/subscriber-codes/revoke.php
Deactivate a subscriber code. The code will no longer accept new redemptions. Existing access already granted remains valid until its own expiration.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| code_id | integer | required | The ID of the code to revoke (from generate or list response) |
Example
curl -X POST https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/revoke.php \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "code_id": 1 }'
Redeem a Code (App)
/subscriber-codes/redeem.php
Used by the My TV Channel app when a viewer opens a universal link. Requires user authentication. Idempotent — re-redeeming the same code returns the existing access.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| code | string | required | The 7-character subscriber code |
Response
{
"success": true,
"message": "Access granted",
"data": {
"channel": {
"id": 42,
"handle": "your-channel",
"name": "Your Channel",
"description": "...",
"thumbnail_url": "..."
},
"access_expires_at": "2026-04-21T14:00:00+00:00",
"already_redeemed": false
}
}
Note: This endpoint is called automatically by the app. You don't need to call it from your server.
Validate a Code (Web Player)
/subscriber-codes/validate.php
Used by the web player to validate a subscriber code before starting playback. No authentication required. Called automatically when a viewer opens a channel link with ?subscriber=.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| code | string | required | The 7-character subscriber code |
| channel_id | integer | required | Channel ID to validate against |
Note: This endpoint is called automatically by the web player. You don't need to call it from your server.
Universal Links
Every code maps to a universal link that works everywhere:
https://{your-channel-handle}.localtvbroadcast.com/?subscriber={CODE}
The link returned in the generate response is ready to share. When a viewer clicks it:
| Scenario | Behavior |
|---|---|
| App installed | Opens My TV Channel app, grants access, shows the channel |
| App not installed | Opens web player in browser, validates code, starts playback |
Codes are case-insensitive. A3F7K2N and a3f7k2n are treated identically. Codes use only the characters 2346789ACDEFGHJKMNPQRTUVWXYZ to avoid ambiguity (no 0/O, 1/I/L, 5/S, 8/B).
Integrations
Stripe Integration
Generate a subscriber code automatically when a Stripe payment succeeds. Add this webhook handler to your server:
// Express.js webhook handler for Stripe
const express = require('express');
const stripe = require('stripe')('sk_...');
const MY_TV_API = 'https://localtvbroadcast.com/api/mytvchannel';
const MY_TV_TOKEN = process.env.MY_TV_TOKEN; // Your JWT token
const CHANNEL_ID = 42; // Your channel ID
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(req.body, sig, 'whsec_...');
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
// Generate a subscriber code
const resp = await fetch(`${MY_TV_API}/subscriber-codes/generate.php`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${MY_TV_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: CHANNEL_ID,
expires_at: '2027-01-01T00:00:00Z',
label: `Stripe ${session.id}`,
price: session.amount_total / 100,
currency: session.currency.toUpperCase(),
max_redemptions: 1,
access_duration_days: 30
})
});
const { data } = await resp.json();
// Send the link to your customer
await sendEmail(session.customer_email, {
subject: 'Your access link',
body: `Watch here: ${data.url}`
});
}
res.json({ received: true });
});
# Flask webhook handler for Stripe
import stripe, requests, os
from flask import Flask, request, jsonify
app = Flask(__name__)
stripe.api_key = 'sk_...'
MY_TV_API = 'https://localtvbroadcast.com/api/mytvchannel'
MY_TV_TOKEN = os.environ['MY_TV_TOKEN']
CHANNEL_ID = 42
@app.route('/webhooks/stripe', methods=['POST'])
def stripe_webhook():
sig = request.headers['Stripe-Signature']
event = stripe.Webhook.construct_event(request.data, sig, 'whsec_...')
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
# Generate a subscriber code
resp = requests.post(
f'{MY_TV_API}/subscriber-codes/generate.php',
headers={
'Authorization': f'Bearer {MY_TV_TOKEN}',
'Content-Type': 'application/json'
},
json={
'channel_id': CHANNEL_ID,
'expires_at': '2027-01-01T00:00:00Z',
'label': f'Stripe {session["id"]}',
'price': session['amount_total'] / 100,
'currency': session['currency'].upper(),
'max_redemptions': 1,
'access_duration_days': 30
}
)
url = resp.json()['data']['url']
send_email(session['customer_email'], f'Watch here: {url}')
return jsonify(received=True)
// PHP webhook handler for Stripe
<?php
$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = \Stripe\Webhook::constructEvent($payload, $sig, 'whsec_...');
$myTvApi = 'https://localtvbroadcast.com/api/mytvchannel';
$myTvToken = getenv('MY_TV_TOKEN');
$channelId = 42;
if ($event->type === 'checkout.session.completed') {
$session = $event->data->object;
// Generate a subscriber code
$ch = curl_init("{$myTvApi}/subscriber-codes/generate.php");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer {$myTvToken}",
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'channel_id' => $channelId,
'expires_at' => '2027-01-01T00:00:00Z',
'label' => "Stripe {$session->id}",
'price' => $session->amount_total / 100,
'currency' => strtoupper($session->currency),
'max_redemptions' => 1,
'access_duration_days' => 30
])
]);
$resp = json_decode(curl_exec($ch));
curl_close($ch);
// Send the link to your customer
mail($session->customer_email, 'Your access link', "Watch here: {$resp->data->url}");
}
http_response_code(200);
echo json_encode(['received' => true]);
Patreon Integration (Self-Hosted)
If you prefer to run your own webhook handler instead of using our hosted integration, here's how to handle the members:pledge:create event on your server:
// Node.js - Patreon webhook handler
const crypto = require('crypto');
app.post('/webhooks/patreon', express.json(), async (req, res) => {
// Verify Patreon signature
const signature = req.headers['x-patreon-signature'];
const hash = crypto
.createHmac('md5', process.env.PATREON_WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
if (signature !== hash) return res.status(403).send('Invalid signature');
const event = req.headers['x-patreon-event'];
const { data, included } = req.body;
if (event === 'members:pledge:create') {
const patron = included.find(i => i.type === 'user');
const email = patron?.attributes?.email;
const amountCents = data.attributes.currently_entitled_amount_cents;
// Only grant access for tiers >= $5
if (amountCents >= 500) {
const resp = await fetch(
'https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/generate.php',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MY_TV_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 42,
expires_at: '2027-01-01T00:00:00Z',
label: `Patreon: ${email}`,
max_redemptions: 1,
access_duration_days: 30
})
}
);
const { data: codeData } = await resp.json();
// Send via Patreon message or your own email service
await sendEmail(email, {
subject: 'Your TV Channel access is ready!',
body: `Thanks for your support! Watch here: ${codeData.url}`
});
}
}
res.json({ received: true });
});
Generic Webhook / Custom Payment
For any payment system with webhooks (PayPal, Gumroad, LemonSqueezy, Ko-fi, Buy Me a Coffee, etc.), the pattern is the same:
Receive payment webhook
Verify the webhook signature per your provider's documentation.
Call generate endpoint
POST /subscriber-codes/generate.php with your JWT token.
Deliver the link
Send data.url to your customer via email, redirect, or in-app message.
Minimal cURL Example
# Generate a single-use code valid for 30 days of access
curl -X POST https://localtvbroadcast.com/api/mytvchannel/subscriber-codes/generate.php \
-H "Authorization: Bearer $MY_TV_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"channel_id": 42,
"expires_at": "2027-01-01T00:00:00Z",
"max_redemptions": 1,
"access_duration_days": 30
}'
# Response includes the link to share:
# "url": "https://your-channel.localtvbroadcast.com/?subscriber=A3F7K2N"
Error Handling
| HTTP Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Missing or invalid fields (check message for details) |
| 401 | Invalid or expired JWT token |
| 403 | Channel not owned by authenticated user |
| 404 | Invalid or expired subscriber code (redeem/validate) |
| 500 | Server error (retry) |