# Monetize Your Channel Your Way

<!--
Source: https://my-tv-channel.com/pay-per-view/
Site: My TV Channel - https://my-tv-channel.com
Published: 2026-07-03
Summary: Monetize your TV channel your way. Connect Patreon, embed a widget on your website, or integrate via API. You keep 100% of the revenue.
-->

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 checkout

### Revolut Business

Accept payments via Revolut checkout. Fast setup, supports multiple currencies.

Hosted checkout

### PayPal

Accept PayPal payments. Buyers pay with their PayPal account or credit card.

Hosted checkout

### Creator Platforms — they handle payment, we handle access

### Patreon

Patrons automatically get access when they pledge at your minimum tier.

Webhook

### Gumroad

Sell access as a Gumroad product. Buyers get their link after purchase.

Webhook

### LemonSqueezy

Sell access through your Lemon Squeezy store. Handles tax and payments.

Webhook

### Ko-fi

Supporters who buy you a "coffee" or join your membership get channel access.

Webhook

### Buy Me a Coffee

Supporters and members automatically receive access to your channel.

Webhook

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

1

#### Sign in below

Use the same Apple account you use in the My TV Channel app.

2

#### Pick provider & enter keys

Select your payment provider and enter your API key or webhook secret. Set price and duration.

3

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

Sign in with Apple

Signed in as .

Channel

Payment Provider

API Secret Key

API Client Secret

Price

Access Duration (days)

Product Name (optional)

Webhook Signing Secret (optional, for signature verification)

Webhook Secret

Access Duration (days)

Minimum Amount ($)

**Where to find your Stripe API key:** Go to [Stripe Dashboard](https://dashboard.stripe.com/apikeys) → 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 Now

This link is personal. It gives you 30 days of access.

For Developers

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.

1

#### Customer pays you

On your website, Patreon, or any payment system you choose.

2

#### Your server calls our API

Generate a subscriber code with the expiration and limits you want.

3

#### Customer gets a link

Like `https://your-channel.my-tv-channel.com/?subscriber=ABC2DEF`

4

#### 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://my-tv-channel.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://my-tv-channel.com/api/mytvchannel/subscriber-codes/
```

API Reference

## Generate a Code

POST `/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://my-tv-channel.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.my-tv-channel.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

GET `/subscriber-codes/list.php?channel_id={id}`

Retrieve all subscriber codes for your channel, with redemption stats.

### Example

```
curl https://my-tv-channel.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.my-tv-channel.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

POST `/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://my-tv-channel.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)

POST `/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)

POST `/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}.my-tv-channel.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://my-tv-channel.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://my-tv-channel.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://my-tv-channel.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](https://my-tv-channel.com/pay-per-view/#connect), 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://my-tv-channel.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:

1

#### Receive payment webhook

Verify the webhook signature per your provider's documentation.

2

#### Call generate endpoint

`POST /subscriber-codes/generate.php` with your JWT token.

3

#### 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://my-tv-channel.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.my-tv-channel.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) |
