Skip to content

Subscription Management

Subscriptions are the relationships between subscribers and channel packages that determine which channels each subscriber has access to. The subscription system is the key mechanism for monetizing IPTV services in Catena.

What is a Subscription

A subscription in Catena is an active link between a subscriber and a channel package. When a subscriber has a subscription to a package, they automatically get access to all channels included in that package.

Key concept:

Subscriber → Subscription → Package → Channels → Viewing
  1. Subscriber subscribes to one or more packages
  2. Each package contains a set of channels
  3. Subscriber gets access to all channels from all their packages
  4. System checks subscription presence when viewing

Key capabilities:

  • Flexible access control — connecting and disconnecting packages in real-time
  • Multiple subscriptions — subscriber can be subscribed to multiple packages simultaneously
  • Free packages — automatic access to basic content for all subscribers
  • Logging — complete history of all subscription changes
  • API-first approach — easy integration with billing systems

Typical workflow:

  1. Billing system receives payment from user
  2. Billing calls Catena API to create subscription
  3. Catena immediately grants access to package channels
  4. Subscriber starts watching channels
  5. At period end, billing disconnects subscription
  6. Access to paid channels is automatically blocked

Subscription Lifecycle

Creating a Subscription

When subscription is created:

  • Upon package payment through billing system
  • Upon manual connection by administrator
  • Upon promo code or bonus activation
  • Upon trial period provision

What happens when created:

  1. Record is created in database about subscriber-package link
  2. Subscriber immediately gets access to all package channels
  3. Record is added to operations log (type createPackageSubscriber)
  4. On next player request, available channels list is updated

Active Subscription

During active subscription:

  • Subscriber can watch all package channels without restrictions
  • System logs all viewing sessions
  • Subscriber's packages field contains active package IDs
  • Streaming server verifies access rights on each stream request

Cancelling a Subscription

When subscription is cancelled:

  • Upon paid period expiration
  • Upon subscription cancellation by user
  • Upon subscriber blocking by administrator
  • Upon package deletion from system

What happens when cancelled:

  1. Subscriber-package link record is deleted
  2. Subscriber immediately loses access to package channels
  3. Record is added to operations log (type deletePackageSubscriber)
  4. Active viewing sessions of package channels are interrupted

Creating a Subscription

Via Web Interface

  1. Open subscriber card in "Subscribers" section
  2. Go to "Subscriptions" or "Packages" tab
  3. Click "Add Subscription"
  4. Select package from dropdown list of available packages
  5. Confirm addition

Subscriber immediately gets access to all channels of the selected package.

Via Management API

Create subscriber subscription to package:

curl -X POST https://your-catena-domain.com/tv-management/api/v1/packages-subscribers \
  -H "X-Auth-Token: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberId": "sKl9SW3AAAE.",
    "packageId": "pKl9SW3AAAE."
  }'

Request parameters:

  • subscriberId (required) — ID of subscriber to connect package to
  • packageId (required) — ID of package to connect

Response:

{
  "subscriberId": "sKl9SW3AAAE.",
  "packageId": "pKl9SW3AAAE.",
  "portalId": "pKl9SW3AAAE."
}

Important points:

  • Subscriber and package must belong to same portal
  • If subscription already exists, API will return error
  • Changes take effect immediately
  • Operation is recorded in log

Deleting a Subscription

Via Web Interface

  1. Open subscriber card
  2. Go to "Subscriptions" tab
  3. Find package in active subscriptions list
  4. Click "Delete" or "Disconnect"
  5. Confirm disconnection

Subscriber immediately loses access to channels of this package.

Via Management API

Delete subscriber subscription to package:

curl -X DELETE https://your-catena-domain.com/tv-management/api/v1/packages-subscribers \
  -H "X-Auth-Token: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriberId": "sKl9SW3AAAE.",
    "packageId": "pKl9SW3AAAE."
  }'

Request parameters:

  • subscriberId (required) — subscriber ID
  • packageId (required) — package ID to disconnect

Response:

HTTP 201 - subscription deleted

Important points:

  • If subscription doesn't exist, API will return error
  • Active viewing sessions will be interrupted
  • Changes take effect immediately
  • Operation is recorded in log

Viewing Subscriptions

Specific Subscriber Subscriptions

Get subscriber's package list:

curl -X GET https://your-catena-domain.com/tv-management/api/v1/subscribers/sKl9SW3AAAE. \
  -H "X-Auth-Token: your-api-key"

Response:

{
  "subscriberId": "sKl9SW3AAAE.",
  "portalId": "pKl9SW3AAAE.",
  "name": "John Doe",
  "phoneCountryCode": "1",
  "phone": "2345678901",
  "playback_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "packages": ["pKl9SW3AAAE.", "sportKl9SW3AAAE."]
}

The packages field contains array of IDs of all packages the subscriber is subscribed to.

Specific Package Subscribers

Unfortunately, there's no direct API to get list of package subscribers. Use operations log or get all subscribers and filter by packages:

# Get all subscribers
curl -X GET https://your-catena-domain.com/tv-management/api/v1/subscribers \
  -H "X-Auth-Token: your-api-key" \
  | jq '.subscribers[] | select(.packages[] | contains("pKl9SW3AAAE."))'

Subscription History via Operations Log

Get all subscription operations for specific subscriber:

curl -X GET "https://your-catena-domain.com/tv-management/api/v1/operations?subscriberId=sKl9SW3AAAE.&type=createPackageSubscriber&type=deletePackageSubscriber" \
  -H "X-Auth-Token: your-api-key"

Get operations for specific package:

curl -X GET "https://your-catena-domain.com/tv-management/api/v1/operations?packageId=pKl9SW3AAAE." \
  -H "X-Auth-Token: your-api-key"

Response:

{
  "operations": [
    {
      "operationId": "opKl9SW3AAAE.",
      "type": "createPackageSubscriber",
      "subscriberId": "sKl9SW3AAAE.",
      "packageId": "pKl9SW3AAAE.",
      "portalId": "pKl9SW3AAAE.",
      "createdAt": "2024-10-16T10:05:00Z",
      "payload": {
        "packageId": "pKl9SW3AAAE.",
        "subscriberId": "sKl9SW3AAAE."
      }
    },
    {
      "operationId": "opKl9SW3AAAB.",
      "type": "deletePackageSubscriber",
      "subscriberId": "sKl9SW3AAAE.",
      "packageId": "pKl9SW3AAAE.",
      "portalId": "pKl9SW3AAAE.",
      "createdAt": "2024-10-20T15:30:00Z",
      "payload": {
        "packageId": "pKl9SW3AAAE.",
        "subscriberId": "sKl9SW3AAAE."
      }
    }
  ],
  "next": "cursor-for-next-page"
}

Operation types:

  • createPackageSubscriber — subscription creation
  • deletePackageSubscriber — subscription deletion
  • autoCreateSubscriber — automatic subscriber creation (may include basic package subscription)

Portal Free Packages

Catena supports the concept of "free packages" — packages that are automatically available to all portal subscribers without explicit subscription creation.

Free Packages Concept

How it works:

  • Portal settings define list of free packages
  • All portal subscribers automatically get access to channels from these packages
  • No need to create individual subscriptions for each subscriber
  • Perfect for basic content, demo channels, promotional channels

Use cases:

  • Basic content — public channels available to all
  • Trial period — demo content for new users
  • Promo channels — advertising and informational channels
  • Public interest channels — mandatory distribution channels

Managing Free Packages

View portal free packages:

curl -X GET https://your-catena-domain.com/tv-management/api/v1/portal \
  -H "X-Auth-Token: your-api-key"

Response:

{
  "portalId": "pKl9SW3AAAE.",
  "name": "my-iptv-portal",
  "domain": "iptv.example.com",
  "freePackages": ["basicKl9SW3AAAE.", "demoKl9SW3AAAE."],
  "branding": {
    "title": "My IPTV Service",
    "description": "Premium IPTV streaming"
  }
}

Add package to free list:

curl -X POST https://your-catena-domain.com/tv-management/api/v1/portal/free-packages/basicKl9SW3AAAE. \
  -H "X-Auth-Token: your-api-key"

Remove package from free list:

curl -X DELETE https://your-catena-domain.com/tv-management/api/v1/portal/free-packages/basicKl9SW3AAAE. \
  -H "X-Auth-Token: your-api-key"

Important:

  • Free package changes apply to all subscribers instantly
  • On addition — all subscribers get access to package channels
  • On removal — only those without explicit subscription lose access

Billing System Integration

Integration Architecture

Typical scheme:

[Billing System] ←→ [Catena API] ←→ [Streaming Server]
       ↓                  ↓                ↓
   Payments        Subscriptions    Channel Access

Billing responsibilities:

  • Receiving payments from users
  • Managing tariffs and subscription periods
  • Tracking subscription expirations
  • Calling Catena API to connect/disconnect packages

Catena responsibilities:

  • Managing channel access
  • Verifying rights during viewing
  • Logging subscriber activity
  • Providing viewing statistics

Integration Examples

Example 1: Webhook on Payment

Billing sends webhook to your service on successful payment:

from flask import Flask, request
import requests

app = Flask(__name__)

CATENA_API_URL = "https://catena.example.com/tv-management/api/v1"
CATENA_API_KEY = "your-api-key"

@app.route('/billing-webhook', methods=['POST'])
def billing_webhook():
    data = request.json

    if data['event'] == 'payment.success':
        # Payment received - activate subscription
        subscriber_id = get_subscriber_id(data['user_phone'])
        package_id = get_package_id(data['tariff_name'])

        # Create subscription in Catena
        response = requests.post(
            f"{CATENA_API_URL}/packages-subscribers",
            headers={"X-Auth-Token": CATENA_API_KEY},
            json={
                "subscriberId": subscriber_id,
                "packageId": package_id
            }
        )

        if response.status_code == 200:
            return {"status": "ok", "message": "Subscription activated"}
        else:
            return {"status": "error", "message": response.text}, 500

    elif data['event'] == 'subscription.expired':
        # Subscription expired - deactivate
        subscriber_id = get_subscriber_id(data['user_phone'])
        package_id = get_package_id(data['tariff_name'])

        # Delete subscription in Catena
        response = requests.delete(
            f"{CATENA_API_URL}/packages-subscribers",
            headers={"X-Auth-Token": CATENA_API_KEY},
            json={
                "subscriberId": subscriber_id,
                "packageId": package_id
            }
        )

        return {"status": "ok", "message": "Subscription deactivated"}

    return {"status": "ok"}

def get_subscriber_id(phone):
    """Get Catena subscriber ID by phone number"""
    response = requests.get(
        f"{CATENA_API_URL}/subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY}
    )
    subscribers = response.json()['subscribers']

    for sub in subscribers:
        full_phone = f"+{sub['phoneCountryCode']}{sub['phone']}"
        if full_phone == phone:
            return sub['subscriberId']

    # If subscriber not found - create
    return create_subscriber(phone)

def get_package_id(tariff_name):
    """Map tariff name to package ID"""
    tariff_mapping = {
        "basic": "basicKl9SW3AAAE.",
        "premium": "premiumKl9SW3AAAE.",
        "sport": "sportKl9SW3AAAE."
    }
    return tariff_mapping.get(tariff_name)

if __name__ == '__main__':
    app.run(port=5000)

Example 2: Periodic Synchronization

Regular checking and synchronization of subscriptions:

import requests
from datetime import datetime, timedelta

CATENA_API_URL = "https://catena.example.com/tv-management/api/v1"
CATENA_API_KEY = "your-api-key"
BILLING_DB = "postgresql://billing_db"

def sync_subscriptions():
    """Synchronize subscriptions between billing and Catena"""

    # 1. Get active subscriptions from billing
    active_billing_subscriptions = get_active_subscriptions_from_billing()

    # 2. Get all Catena subscribers
    response = requests.get(
        f"{CATENA_API_URL}/subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY}
    )
    catena_subscribers = response.json()['subscribers']

    # 3. Compare and synchronize
    for billing_sub in active_billing_subscriptions:
        phone = billing_sub['phone']
        package_id = get_package_id(billing_sub['tariff'])

        # Find subscriber in Catena
        catena_sub = find_subscriber_by_phone(catena_subscribers, phone)

        if catena_sub:
            # Check if needed subscription exists
            if package_id not in catena_sub['packages']:
                # No subscription - create
                create_subscription(catena_sub['subscriberId'], package_id)
                print(f"Activated: {phone} -> {package_id}")
        else:
            # No subscriber - create with subscription
            create_subscriber_with_package(phone, package_id)
            print(f"Created subscriber: {phone}")

    # 4. Disconnect expired subscriptions
    for catena_sub in catena_subscribers:
        phone = f"+{catena_sub['phoneCountryCode']}{catena_sub['phone']}"

        for package_id in catena_sub['packages']:
            if not has_active_billing_subscription(phone, package_id):
                # No subscription in billing - remove from Catena
                delete_subscription(catena_sub['subscriberId'], package_id)
                print(f"Deactivated: {phone} -> {package_id}")

def create_subscription(subscriber_id, package_id):
    requests.post(
        f"{CATENA_API_URL}/packages-subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY},
        json={
            "subscriberId": subscriber_id,
            "packageId": package_id
        }
    )

def delete_subscription(subscriber_id, package_id):
    requests.delete(
        f"{CATENA_API_URL}/packages-subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY},
        json={
            "subscriberId": subscriber_id,
            "packageId": package_id
        }
    )

# Run this function on schedule (e.g., every hour)
if __name__ == '__main__':
    sync_subscriptions()

Example 3: Trial Period

Automatic trial period for new subscribers:

import requests
from datetime import datetime, timedelta

def activate_trial_subscription(phone, trial_days=7):
    """Activate trial subscription for N days"""

    # 1. Create or get subscriber
    subscriber_id = get_or_create_subscriber(phone)

    # 2. Connect trial package
    trial_package_id = "trialKl9SW3AAAE."

    response = requests.post(
        f"{CATENA_API_URL}/packages-subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY},
        json={
            "subscriberId": subscriber_id,
            "packageId": trial_package_id
        }
    )

    if response.status_code == 200:
        # 3. Schedule automatic cancellation
        schedule_subscription_cancellation(
            subscriber_id,
            trial_package_id,
            datetime.now() + timedelta(days=trial_days)
        )

        return {
            "success": True,
            "message": f"Trial activated for {trial_days} days",
            "expires_at": (datetime.now() + timedelta(days=trial_days)).isoformat()
        }

    return {"success": False, "error": response.text}

def schedule_subscription_cancellation(subscriber_id, package_id, cancel_date):
    """Schedule subscription cancellation"""
    # Save to task database or use scheduler
    # E.g., Celery, APScheduler, or cron job
    pass

Typical Use Cases

Auto-Renewal Subscription

Task: Implement monthly subscription with automatic renewal

Solution:

  1. Billing charges payment each month
  2. On successful charge, billing checks subscription presence in Catena
  3. If subscription exists — do nothing (it's already active)
  4. If no subscription — create via API
  5. On failed charge — delete subscription via API
def process_monthly_renewal(user_id, package_name):
    """Process monthly renewal"""

    # Charge attempt
    payment_success = billing_charge(user_id, get_package_price(package_name))

    subscriber_id = get_subscriber_id_by_user(user_id)
    package_id = get_package_id(package_name)

    if payment_success:
        # Payment successful - ensure subscription is active
        ensure_subscription_active(subscriber_id, package_id)
    else:
        # Payment failed - disconnect subscription
        deactivate_subscription(subscriber_id, package_id)
        send_notification(user_id, "payment_failed")

Family Subscription

Task: One payment — access for multiple subscribers (family account)

Solution:

  1. Create family tariff in billing
  2. On payment, connect package to all family subscribers
  3. Store link between subscribers in billing
def activate_family_subscription(family_id, package_name):
    """Activate family subscription"""

    package_id = get_package_id(package_name)

    # Get all family members from billing
    family_members = get_family_members(family_id)

    for member in family_members:
        subscriber_id = get_subscriber_id(member['phone'])

        # Connect package to each
        requests.post(
            f"{CATENA_API_URL}/packages-subscribers",
            headers={"X-Auth-Token": CATENA_API_KEY},
            json={
                "subscriberId": subscriber_id,
                "packageId": package_id
            }
        )

    return {"activated": len(family_members)}

Temporary Promotion

Task: Give access to premium channels for the weekend

Solution:

  1. Friday evening — connect promo package to all active subscribers
  2. Monday morning — disconnect promo package
#!/bin/bash
# friday-promo.sh - run via cron on Friday at 6 PM

PROMO_PACKAGE_ID="weekendKl9SW3AAAE."

# Get all subscribers
SUBSCRIBERS=$(curl -s -X GET "$CATENA_API_URL/subscribers" \
  -H "X-Auth-Token: $CATENA_API_KEY" \
  | jq -r '.subscribers[].subscriberId')

# Connect promo package to each
for SUBSCRIBER_ID in $SUBSCRIBERS; do
  curl -X POST "$CATENA_API_URL/packages-subscribers" \
    -H "X-Auth-Token: $CATENA_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"subscriberId\": \"$SUBSCRIBER_ID\",
      \"packageId\": \"$PROMO_PACKAGE_ID\"
    }"
done

echo "Promo activated for $(echo "$SUBSCRIBERS" | wc -l) subscribers"
#!/bin/bash
# monday-cleanup.sh - run via cron on Monday at 6 AM

PROMO_PACKAGE_ID="weekendKl9SW3AAAE."

SUBSCRIBERS=$(curl -s -X GET "$CATENA_API_URL/subscribers" \
  -H "X-Auth-Token: $CATENA_API_KEY" \
  | jq -r '.subscribers[] | select(.packages[] | contains("'$PROMO_PACKAGE_ID'")) | .subscriberId')

for SUBSCRIBER_ID in $SUBSCRIBERS; do
  curl -X DELETE "$CATENA_API_URL/packages-subscribers" \
    -H "X-Auth-Token: $CATENA_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"subscriberId\": \"$SUBSCRIBER_ID\",
      \"packageId\": \"$PROMO_PACKAGE_ID\"
    }"
done

echo "Promo deactivated for $(echo "$SUBSCRIBERS" | wc -l) subscribers"

Plan Downgrade

Task: Subscriber moves from premium to basic plan

Solution:

def downgrade_subscription(subscriber_id, from_package, to_package):
    """Downgrade subscriber plan"""

    from_package_id = get_package_id(from_package)
    to_package_id = get_package_id(to_package)

    # 1. Disconnect premium package
    requests.delete(
        f"{CATENA_API_URL}/packages-subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY},
        json={
            "subscriberId": subscriber_id,
            "packageId": from_package_id
        }
    )

    # 2. Connect basic package
    requests.post(
        f"{CATENA_API_URL}/packages-subscribers",
        headers={"X-Auth-Token": CATENA_API_KEY},
        json={
            "subscriberId": subscriber_id,
            "packageId": to_package_id
        }
    )

    # 3. Record in billing
    billing_record_downgrade(subscriber_id, from_package, to_package)

    return {"success": True, "new_package": to_package}

Best Practices

Package Design

Package structure recommendations:

  • Basic package — minimal channel set for all
  • Thematic packages — sports, movies, kids, news
  • Premium packages — exclusive content, HD/4K channels
  • Combo packages — multiple themes in one (savings for subscriber)

Avoid:

  • Too many small packages — complicates choice
  • Channel duplication between packages — billing confusion
  • Package overlaps without logic — one channel in 5 different packages

Error Handling

When integrating with billing:

def safe_create_subscription(subscriber_id, package_id, retry_count=3):
    """Create subscription with retries"""

    for attempt in range(retry_count):
        try:
            response = requests.post(
                f"{CATENA_API_URL}/packages-subscribers",
                headers={"X-Auth-Token": CATENA_API_KEY},
                json={
                    "subscriberId": subscriber_id,
                    "packageId": package_id
                },
                timeout=10
            )

            if response.status_code == 200:
                return {"success": True}
            elif response.status_code == 409:
                # Subscription already exists - this is OK
                return {"success": True, "already_exists": True}
            else:
                # Other error
                error_msg = response.json().get('message', 'Unknown error')
                log_error(f"Failed to create subscription: {error_msg}")

        except requests.exceptions.Timeout:
            log_warning(f"Timeout on attempt {attempt + 1}")
            if attempt < retry_count - 1:
                time.sleep(2 ** attempt)  # Exponential backoff
            continue
        except Exception as e:
            log_error(f"Unexpected error: {str(e)}")
            break

    # All attempts failed - save for manual processing
    save_failed_operation("create_subscription", subscriber_id, package_id)
    return {"success": False, "error": "Failed after retries"}

State Synchronization

Regular data reconciliation:

def audit_subscriptions():
    """Check subscription consistency between systems"""

    discrepancies = []

    # Get data from both systems
    billing_subscriptions = get_billing_subscriptions()
    catena_subscriptions = get_catena_subscriptions()

    # Find discrepancies
    for billing_sub in billing_subscriptions:
        if not exists_in_catena(billing_sub, catena_subscriptions):
            discrepancies.append({
                "type": "missing_in_catena",
                "subscriber": billing_sub['phone'],
                "package": billing_sub['package']
            })

    for catena_sub in catena_subscriptions:
        if not exists_in_billing(catena_sub, billing_subscriptions):
            discrepancies.append({
                "type": "missing_in_billing",
                "subscriber": catena_sub['phone'],
                "package": catena_sub['package']
            })

    if discrepancies:
        # Send notification to administrator
        send_audit_report(discrepancies)

        # Optionally: auto-fix
        auto_fix_discrepancies(discrepancies)

    return discrepancies

Logging and Monitoring

What to log:

  • All subscription creations and deletions
  • Errors when calling API
  • Catena API response time
  • Discrepancies between billing and Catena

Metrics to track:

import prometheus_client as prom

# Prometheus metrics
subscription_creations = prom.Counter(
    'catena_subscription_creations_total',
    'Total number of subscription creations',
    ['package_name', 'status']
)

subscription_deletions = prom.Counter(
    'catena_subscription_deletions_total',
    'Total number of subscription deletions',
    ['package_name', 'status']
)

api_latency = prom.Histogram(
    'catena_api_latency_seconds',
    'Latency of Catena API calls',
    ['endpoint', 'method']
)

def monitored_create_subscription(subscriber_id, package_id):
    """Create subscription with monitoring"""

    package_name = get_package_name(package_id)

    with api_latency.labels('/packages-subscribers', 'POST').time():
        try:
            response = requests.post(...)

            if response.status_code == 200:
                subscription_creations.labels(package_name, 'success').inc()
                return {"success": True}
            else:
                subscription_creations.labels(package_name, 'error').inc()
                return {"success": False}

        except Exception as e:
            subscription_creations.labels(package_name, 'exception').inc()
            raise

Subscriber Notifications

When to send notifications:

  1. On subscription activation — "Welcome! Now available channels: ..."
  2. 3 days before expiration — "Your subscription expires in 3 days"
  3. On renewal — "Subscription renewed until ..."
  4. On cancellation — "Subscription cancelled. To renew..."
  5. On payment error — "Failed to charge. Please check..."
def notify_subscription_activated(subscriber_id, package_name):
    """Notification about subscription activation"""

    subscriber = get_subscriber(subscriber_id)
    phone = f"+{subscriber['phoneCountryCode']}{subscriber['phone']}"

    # Get package channel list
    package = get_package(get_package_id(package_name))
    channels = ", ".join(package['channels'][:5])  # First 5 channels

    message = f"""
    🎉 Subscription activated!

    Package: {package_name}
    Available channels: {channels} and more

    Enjoy watching!
    """

    send_sms(phone, message)

Troubleshooting

Subscription Not Creating

Possible causes:

  • Invalid subscriberId or packageId
  • Subscriber and package from different portals
  • Subscription already exists
  • API authorization issues

Solution:

  1. Check subscriber existence: GET /subscribers/{id}
  2. Check package existence: GET /packages/{id}
  3. Ensure portalId matches
  4. Check subscriber's current subscriptions
  5. Verify API key validity

Subscriber Doesn't See Channels After Subscription Creation

Possible causes:

  • Package contains no channels
  • App didn't update channel list
  • Streaming server issues

Solution:

  1. Check package contents: GET /packages/{id}
  2. Ensure package has channels
  3. Ask subscriber to restart app
  4. Check subscriber's playback_token
  5. Check streaming server logs

Subscription Not Deleting

Possible causes:

  • Subscription doesn't exist (already deleted)
  • Invalid request parameters
  • It's a portal free package (can't delete)

Solution:

  1. Check subscriber's current subscriptions
  2. Ensure it's not a portal free package
  3. Verify subscriberId and packageId correctness
  4. Check operations log for this subscriber

Discrepancies Between Billing and Catena

Problem: Subscription active in billing but not in Catena (or vice versa)

Solution:

  1. Implement regular synchronization (every 15-60 minutes)
  2. Use operations log to identify issues
  3. On discrepancy, billing has priority (source of truth)
  4. Log all changes for analysis
def fix_sync_issue(subscriber_id):
    """Fix desynchronization for subscriber"""

    # 1. Get "truth" from billing
    billing_packages = get_billing_packages(subscriber_id)

    # 2. Get current state in Catena
    subscriber = get_catena_subscriber(subscriber_id)
    catena_packages = subscriber['packages']

    # 3. Synchronize
    for package_id in billing_packages:
        if package_id not in catena_packages:
            # Should be but isn't - add
            create_subscription(subscriber_id, package_id)
            log_info(f"Fixed: added {package_id} to {subscriber_id}")

    for package_id in catena_packages:
        if package_id not in billing_packages:
            # Exists but shouldn't - remove
            delete_subscription(subscriber_id, package_id)
            log_info(f"Fixed: removed {package_id} from {subscriber_id}")

See Also