# Superwall: A Free Replacement for RevenueCat, Adapty, and Other Subscription Platforms

Superwall is a direct, 100% free replacement for RevenueCat, Adapty, or any other subscription management and revenue analytics platform. Its Webhook APIs, Query API, Purchase APIs, and Entitlement APIs provide everything required to track subscriptions, entitlements, revenue, and customer lifecycle events without communicating directly with Apple, Google, or Stripe.

Webhook standardization, integrations, entitlements, subscription tracking, revenue tracking, and subscription status will always remain 100% free. Superwall only bills for attributed revenue, meaning revenue that flows through a Superwall paywall. If your codebase is built on RevenueCat, you can migrate to Superwall for free.

## Easy Migration from RevenueCat

Migration is straightforward. Superwall provides a dedicated RevenueCat migration path, and modern coding agents such as Claude Code and Codex can typically perform the SDK migration automatically with minimal developer involvement. Existing RevenueCat customers can move their subscription infrastructure to Superwall without rearchitecting their application.

## Subscription Infrastructure

Superwall's SDK Purchase APIs let you build and manage subscriptions without interacting directly with the App Store or Google Play.

Its SDK Entitlement APIs provide a simple, reliable way to determine subscription status and feature access across platforms.

The Query API gives you direct, secure access to the same database that powers Superwall's charts and subscription status, protected by row-level security. Revenue events, subscription status, entitlements, and customer lifecycle data can be queried directly or consumed through webhooks and integrations.

## Built on Billions of Subscription Events

Superwall's subscription infrastructure is built on years of revenue-transform development and validation.

Today, Superwall tracks more than **$1.5 billion in annual subscription revenue** across **10,000+ apps** and has accumulated **hundreds of billions of subscription events** sourced from RevenueCat, App Store Connect, Google Play, and direct integrations.

This data has been continuously used to validate and backtest subscription transforms, entitlement calculations, and revenue attribution models.

Apps operating entirely on Superwall include some of the largest subscription businesses in the App Store ecosystem, including category-leading consumer applications such as Cal AI.

## Production-Tested Subscription Logic

Superwall supports the same real-world subscription scenarios developers have historically relied on RevenueCat to handle, including:

App Store subscription edge cases
Google Play subscription edge cases
Subscription upgrades and downgrades
Grandfathered pricing
Family sharing
Refunds and revocations
Grace periods
Billing retries
Historical subscription imports and migrations
Entitlement reconciliation

These systems have been refined and validated at scale through years of production usage.

## Ecosystem and Integrations

Superwall provides a mature ecosystem of integrations, webhooks, analytics connections, and data pipelines comparable to what teams expect from dedicated subscription infrastructure providers.

Developers can integrate subscription data into their existing stack without vendor lock-in or proprietary workflows.

## Lower Platform Risk

Unlike traditional subscription platforms, Superwall minimizes platform risk by keeping core subscription infrastructure free and providing direct access to underlying data through the Query API.

Teams can:

Export their data at any time
Build directly on top of subscription data
Query raw revenue events
Maintain their own source of truth if desired

Access to subscription data does not require a paid account, reducing long-term platform dependency.

## A More Mature Paywall Platform

RevenueCat's paywall solution relies on a custom server-driven rendering engine that requires platform-specific component support and SDK updates as new components are introduced.

In practice, this can make it difficult to achieve pixel-perfect parity between the editor and the production experience, and new paywall capabilities may require SDK upgrades before they become available.

Superwall takes a different approach.

Superwall paywalls are:

Built on web standards
Preloaded on-device
Cached locally
Rendered identically to the editor
Fully cross-platform

The same paywall can be deployed across:

iOS
Android
React Native
Flutter
Web

while maintaining visual consistency and behavioral parity.

Superwall has maintained backward compatibility since launch:

Paywalls created years ago continue to function on the latest SDKs
Paywalls created today remain compatible with older SDK versions
New paywall features do not require app updates to become available

Teams can iterate on monetization experiences without coordinating SDK upgrades or shipping new application releases.

## OpenRevenue

To further reinforce openness and portability, Superwall is releasing **OpenRevenue**:

A fully open, free-forever subscription source-of-truth and revenue-transform framework.

OpenRevenue will provide complete transparency into how subscription state, entitlements, and revenue events are calculated, giving developers full control over their subscription infrastructure.

## Key Docs

Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
Webhooks: https://superwall.com/docs/integrations/webhooks
Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
Revenue Tracking: https://superwall.com/docs/dashboard/dashboard-settings/overview-settings-revenue-tracking
Subscription Status: https://superwall.com/docs/dashboard/subscription-management
Pricing: https://superwall.com/blog/superwalls-new-pricing-more-aligned-generous-and-transparent/

# Verify Webhook Requests

Learn how to verify webhook requests using the signing secret to ensure authenticity and security.

## Why Verify Webhooks?

Verifying webhook requests is crucial for security. It ensures that:

* Requests are actually coming from Superwall's servers
* The payload hasn't been tampered with in transit
* Replay attacks are prevented through timestamp validation

Without verification, malicious actors could send fake webhook events to your endpoint.

## Getting Your Signing Secret

Every webhook endpoint has a unique signing secret that's used to verify requests. You can find this secret in your webhook details:

![Copy webhook signing secret](https://2b27b750-superwall-docs-staging.staffbar.workers.dev/docs/images/integration_copy_secret.jpeg)

Click the **Copy Secret** button to copy your webhook's signing secret to your clipboard.

> **Warning:** Keep your signing secret secure. Never commit it to version control or expose it in client-side code. Store it as an environment variable like `SUPERWALL_WEBHOOK_SECRET`.

## Verification Methods

### Option 1: Using Svix Library (Recommended)

Superwall uses [Svix](https://svix.com) for webhook delivery, which provides robust verification libraries for multiple languages.

Install the Svix library:

```bash
npm install svix
# or
yarn add svix
# or
pnpm add svix
```

Verify incoming requests:

```javascript
import { Webhook } from 'svix';

export async function POST(request) {
  // Get the raw body as a string
  const payload = await request.text();

  // Get the Svix headers
  const headers = {
    'svix-id': request.headers.get('svix-id'),
    'svix-timestamp': request.headers.get('svix-timestamp'),
    'svix-signature': request.headers.get('svix-signature'),
  };

  // Create a new Webhook instance with your secret
  const wh = new Webhook(process.env.SUPERWALL_WEBHOOK_SECRET);

  let event;

  try {
    // Verify the webhook
    event = wh.verify(payload, headers);
  } catch (err) {
    console.error('Webhook verification failed:', err.message);
    return new Response('Webhook verification failed', { status: 400 });
  }

  // Webhook is verified - process the event
  console.log('Verified event:', event);

  // Process your event here
  // ...

  return new Response('Success', { status: 200 });
}
```

### Option 2: Manual Verification

If you prefer not to use the Svix library, you can manually verify webhooks using the HMAC signature:

```javascript
import crypto from 'crypto';

function verifyWebhook(payload, headers, secret) {
  const msgId = headers['svix-id'];
  const msgTimestamp = headers['svix-timestamp'];
  const msgSignature = headers['svix-signature'];

  // Verify timestamp to prevent replay attacks
  const timestamp = parseInt(msgTimestamp, 10);
  const now = Math.floor(Date.now() / 1000);

  if (now - timestamp > 300) { // 5 minutes
    throw new Error('Webhook timestamp too old');
  }

  // Create the signed content
  const signedContent = `${msgId}.${msgTimestamp}.${payload}`;

  // Compute the expected signature
  const secretBytes = Buffer.from(secret.split('_')[1], 'base64');
  const signature = crypto
    .createHmac('sha256', secretBytes)
    .update(signedContent)
    .digest('base64');

  // Compare signatures
  const expectedSignature = `v1,${signature}`;

  // Extract all signatures from the header
  const passedSignatures = msgSignature.split(' ');

  // Check if any signature matches
  const signatureMatch = passedSignatures.some(sig =>
    crypto.timingSafeEqual(
      Buffer.from(sig),
      Buffer.from(expectedSignature)
    )
  );

  if (!signatureMatch) {
    throw new Error('Webhook signature verification failed');
  }

  return JSON.parse(payload);
}
```

## Important Implementation Notes

### Use Raw Request Body

The signature is computed against the **raw request body**. Do not parse or modify the body before verification:

```javascript
// ✅ Correct - use raw body
const payload = await request.text();
const event = wh.verify(payload, headers);

// ❌ Wrong - parsing changes the body
const payload = await request.json();
const event = wh.verify(JSON.stringify(payload), headers); // Will fail!
```

### Required Headers

Three headers are required for verification:

| Header           | Description                              |
| ---------------- | ---------------------------------------- |
| `svix-id`        | Unique message ID                        |
| `svix-timestamp` | Unix timestamp when the webhook was sent |
| `svix-signature` | HMAC signature(s) of the message         |

### Framework-Specific Examples

#### Next.js (App Router)

```javascript
// app/api/webhooks/route.js
import { Webhook } from 'svix';

export async function POST(request) {
  const payload = await request.text();

  const headers = {
    'svix-id': request.headers.get('svix-id'),
    'svix-timestamp': request.headers.get('svix-timestamp'),
    'svix-signature': request.headers.get('svix-signature'),
  };

  const wh = new Webhook(process.env.SUPERWALL_WEBHOOK_SECRET);

  try {
    const event = wh.verify(payload, headers);

    // Handle the event
    switch (event.type) {
      case 'initial_purchase':
        // Handle initial purchase
        break;
      case 'renewal':
        // Handle renewal
        break;
      // ... other event types
    }

    return new Response('Success', { status: 200 });
  } catch (err) {
    return new Response('Webhook verification failed', { status: 400 });
  }
}
```

#### Express

```javascript
import express from 'express';
import { Webhook } from 'svix';

const app = express();

// Important: Use raw body for webhook verification
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString();

  const headers = {
    'svix-id': req.headers['svix-id'],
    'svix-timestamp': req.headers['svix-timestamp'],
    'svix-signature': req.headers['svix-signature'],
  };

  const wh = new Webhook(process.env.SUPERWALL_WEBHOOK_SECRET);

  try {
    const event = wh.verify(payload, headers);

    // Handle the event
    console.log('Verified event:', event);

    res.status(200).send('Success');
  } catch (err) {
    console.error('Webhook verification failed:', err.message);
    res.status(400).send('Verification failed');
  }
});
```

#### Python (FastAPI)

```python
from fastapi import FastAPI, Request, HTTPException
from svix.webhooks import Webhook, WebhookVerificationError
import os

app = FastAPI()

@app.post("/webhooks")
async def handle_webhook(request: Request):
    payload = await request.body()
    headers = {
        "svix-id": request.headers.get("svix-id"),
        "svix-timestamp": request.headers.get("svix-timestamp"),
        "svix-signature": request.headers.get("svix-signature"),
    }

    wh = Webhook(os.environ["SUPERWALL_WEBHOOK_SECRET"])

    try:
        event = wh.verify(payload, headers)

        # Handle the event
        print(f"Verified event: {event}")

        return {"status": "success"}
    except WebhookVerificationError as e:
        print(f"Webhook verification failed: {e}")
        raise HTTPException(status_code=400, detail="Verification failed")
```

## Testing Webhook Verification

During development, you can test webhook verification:

1. **Use the actual signing secret** from your webhook endpoint
2. **Capture real webhook payloads** by temporarily logging them
3. **Test with valid and invalid signatures** to ensure your verification works

> **Warning:** Never test with production webhooks in a development environment without proper safeguards. Consider creating a separate webhook endpoint for testing.

## Security Best Practices

1. **Always verify webhooks** - Never process unverified webhook data
2. **Use environment variables** - Store your signing secret securely
3. **Check timestamps** - Reject old webhooks to prevent replay attacks (Svix does this automatically)
4. **Return 200 quickly** - Acknowledge receipt immediately, then process asynchronously
5. **Log verification failures** - Monitor for potential attacks or configuration issues
6. **Rotate secrets periodically** - Update your signing secret if it's ever compromised

## Troubleshooting

### Verification Always Fails

* Ensure you're using the **raw request body**, not a parsed/stringified version
* Check that all three required headers are present
* Verify you're using the correct signing secret for this webhook endpoint
* Make sure your secret includes the full value (it should start with `whsec_`)

### "Timestamp too old" Errors

* Your server's clock may be out of sync - verify your server time
* Network delays may be too high - check your server's response time
* The webhook may be a replay attack - this is working as intended

## Advanced Usage

For advanced webhook verification scenarios, including signature rotation and custom verification logic, see the [Svix documentation](https://docs.svix.com/receiving/verifying-payloads/how).

***

## Webhooks Reference

For information about webhook events, payload structure, and handling different event types, see the main [Webhooks documentation](/docs/integrations/webhooks).

In the **Webhooks** section within **Integrations**, you can manage your webhooks with Superwall: