Engineering a Non-Custodial Bitcoin Paywall for an AI Assistant

Leader 1 11
calendar_today agoschedule5 min read

Engineering a Non-Custodial Bitcoin Paywall for an AI Assistant

We recently completed the first real Bitcoin payment for Zorgax, the AI assistant inside the MyZubster ecosystem.

The goal was not simply to display a wallet address. We wanted a complete production flow that could connect a verified on-chain payment to an authenticated account and automatically activate paid features.

The final lifecycle looks like this:

Authentication
      ↓
Payment intent
      ↓
External Bitcoin wallet
      ↓
Blockchain transaction
      ↓
Independent verification
      ↓
Entitlement activation
      ↓
Backend feature access

Zorgax access levels

Zorgax currently provides three plans:

Free
- Basic AI assistant
- Limited web research
- Maximum 2 sources

Pro — €9.90 monthly equivalent
- Advanced web research
- Maximum 5 sources
- Zorgax workspace

Developer — €29.90 monthly equivalent
- Everything in Pro
- Maximum 8 sources
- Direct API access
- Automation features

The current Bitcoin flow creates time-bounded access. It is not an automatic recurring wallet subscription.

Why a wallet address is not enough

A public Bitcoin address can receive payments, but it cannot answer several application-level questions:

  • Which account made the payment?
  • Which plan was selected?
  • What BTC amount was expected?
  • When was the exchange-rate quote generated?
  • Has this transaction already been used?
  • When should the entitlement expire?

For this reason, Zorgax creates a persistent payment intent before displaying the payment instructions.

{
  "intentId": "payment-intent-id",
  "ownerId": "authenticated-user",
  "plan": "pro",
  "asset": "BTC",
  "destination": "bitcoin-address",
  "expectedAmountSats": 14728,
  "status": "PENDING",
  "expiresAt": "quote-expiration"
}

The exact amount varies according to the quote generated for the order.

Creating the checkout

The authenticated frontend requests a new payment intent:

const response = await fetch(
  "/api/zorgax/assistant/checkout/intent",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`
    },
    body: JSON.stringify({
      plan: "pro",
      asset: "BTC"
    })
  }
);

The backend associates the intent with the current user and returns:

  • The BTC destination
  • The exact amount
  • The expiration time
  • The payment-intent identifier

The user then pays from an external wallet.

MyZubster never requests the private key and never signs or broadcasts the transaction.

Verifying the transaction

After broadcasting the payment, the user receives a Bitcoin transaction ID.

The TXID is submitted to the verification endpoint:

await fetch(
  `/api/zorgax/assistant/checkout/intent/${intentId}/verify`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`
    },
    body: JSON.stringify({
      paymentReference: txid
    })
  }
);

The server then retrieves the public transaction and checks:

Transaction exists
        ↓
Correct destination
        ↓
Sufficient amount
        ↓
Required confirmations
        ↓
Intent belongs to the user
        ↓
Payment has not already been consumed

A simplified verifier looks like this:

const transaction = await getBitcoinTransaction(txid);

const output = transaction.outputs.find(
  item => item.address === intent.destination
);

if (!output) {
  throw new Error("Unexpected payment destination");
}

if (output.value < intent.expectedAmountSats) {
  throw new Error("Insufficient payment amount");
}

if (transaction.confirmations < minimumConfirmations) {
  throw new Error("Insufficient blockchain confirmations");
}

Why confirmations matter

During the first live production test, Zorgax detected the transaction while it was still in the mempool.

The application returned:

Payment not activated:
Insufficient blockchain confirmations

This was expected.

Seeing a transaction in the mempool does not mean the system should immediately grant paid access. Zorgax waited until the payment entered a Bitcoin block.

After the required confirmation, the same TXID was verified again:

Payment verified
Access ACTIVE
Plan Pro

No second payment was required.

Activating the entitlement

After successful verification, Zorgax creates or updates the account’s entitlement:

{
  "plan": "pro",
  "tier": "PRO",
  "status": "ACTIVE",
  "source": "BITCOIN_PAYMENT",
  "startsAt": "activation-date",
  "expiresAt": "expiration-date"
}

Every protected request evaluates this access state before running the requested operation.

Enforcing access on the backend

Hiding a button in the frontend is not security.

The backend applies an access policy to every Zorgax request:

const policies = {
  guest: {
    chat: true,
    webResearch: false,
    workspace: false,
    directApi: false
  },

  free: {
    chat: true,
    webResearch: true,
    maxWebResults: 2,
    workspace: false,
    directApi: false
  },

  pro: {
    chat: true,
    webResearch: true,
    maxWebResults: 5,
    workspace: true,
    directApi: false
  },

  developer: {
    chat: true,
    webResearch: true,
    maxWebResults: 8,
    workspace: true,
    directApi: true
  }
};

For example, direct research requires the Developer tier:

router.get(
  "/research",
  authenticate,
  requireZorgaxPlan("developer"),
  researchHandler
);

Workspace persistence requires Pro or higher:

router.post(
  "/data/commit",
  authenticate,
  requireZorgaxPlan("pro"),
  commitHandler
);

Guest access without silent authentication failures

Zorgax supports guest chat, but an invalid token must never silently become a guest session.

The authentication boundary is:

No token
→ Guest access

Valid token
→ Authenticated access

Invalid token
→ 401 Unauthorized

This distinction prevents expired or manipulated credentials from bypassing protected logic.

Unifying legacy subscriptions and new entitlements

MyZubster already had legacy subscription records. The new payment implementation introduced a more explicit entitlement model.

We created one access service that evaluates both:

Legacy subscriptions ─┐
                      ├── Unified Zorgax access
New entitlements ─────┘

The service selects the highest active plan and returns a single feature policy.

This allows existing accounts to remain compatible while new Bitcoin payments use the improved lifecycle.

A Vercel routing problem

The application also contains a generic Zorgax AI gateway route.

A broad rule such as:

/api/zorgax/*

could intercept requests intended for the monetization backend.

The specific monetization route now takes priority:

{
  "source": "/api/zorgax/monetization/(.*)",
  "destination": "/api/index.js"
}

Only afterward does the generic AI proxy apply.

This ensures that purchases and entitlements are handled by the backend containing the correct authentication middleware and database connection.

Confirming persistent AI writes

Zorgax can prepare structured data, but it does not silently persist AI-generated content.

The workspace flow requires:

  1. A generated preview.
  2. A cryptographic digest of that preview.
  3. An explicit user confirmation.
  4. Digest validation before persistence.
const expectedDigest = digestPreview(preview);

if (expectedDigest !== submittedDigest) {
  throw new Error("Preview changed");
}

This creates a clear boundary between AI suggestions and authorized writes.

Production verification

Before deployment, the Zorgax test family completed:

58 test suites
206 tests passed

Production checks confirmed that:

Public pricing                  → 200
Guest chat                      → 200
Guest web research              → disabled
Protected monetization routes   → 401 without login
Developer research API          → 401 without login
Checkout                        → 401 without login

The real Bitcoin transaction then proved the final missing part: verified payment-to-entitlement activation.

Security boundaries

The production design follows these rules:

  • No private keys are accepted.
  • MyZubster does not sign transactions.
  • Users pay from external wallets.
  • The destination and amount are verified.
  • Blockchain confirmations are required.
  • Payment intents belong to authenticated users.
  • Paid features are enforced server-side.
  • Persistent writes require explicit confirmation.
  • Invalid tokens never become anonymous sessions.

What comes next

The current system is operational but intentionally conservative.

Possible next improvements include:

  • Renewal notifications
  • Payment-history dashboards
  • Downloadable receipts
  • Additional independent Bitcoin providers
  • More supported settlement assets
  • Entitlement-expiration reminders
  • Better transaction monitoring

Every new asset will require its own trusted quote source, verification logic and confirmation policy.

Final result

Zorgax monetization is no longer only a pricing page.

A real production payment successfully completed this sequence:

BTC payment
→ blockchain confirmation
→ independent verification
→ entitlement activation
→ Pro features enabled

The result was:

Access: ACTIVE
Plan: Pro

Zorgax:

https://www.myzubster.com/zorgax

MyZubster source code:

https://github.com/MyZubster-Ecosystem/myzubster

Production monetization implementation:

https://github.com/MyZubster-Ecosystem/myzubster/commit/d7a579cd89801d29ac094a19af42fb17350691ef

🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

Why Email-Only Contact Forms Are Failing in 2026 (And What Developers Should Do Instead)

JayCode - Mar 2

We Completed the First Live Bitcoin-to-Pro Activation on MyZubster

Myzubster - Aug 31

The Zero-Net-Loss Fleet & The Mercenary Squad: A Live AI Economy

DEVPlank - Aug 4

From a Bitcoin Transaction to a Paid AI Subscription — Without Holding Private Keys

Myzubster - Aug 28

7 Best Tools for Founders Building in Public (2026 Guide)

Udit060 - Jul 15
chevron_left
1.2k Points12 Badges
Rimini
18Posts
1Comments
1Connections

Related Jobs

View all jobs →

Commenters (This Week)

2 comments
2 comments
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!