Skip to content

Automatically Pay Cloudflare Invoices

Scenario: Your AI DevOps agent monitors a billing email inbox. When it detects a Cloudflare invoice, it uses an LLM to extract the invoice amount, validates it against a PQSafe spend envelope, dispatches via the Wise or Stripe rail, and posts a Slack notification.

Prerequisites

  • @pqsafe/agent-pay installed
  • Wise account with API access or Stripe account with a stored card
  • Email inbox access (e.g., Gmail API, IMAP)
  • Slack webhook URL for notifications
  • Cloudflare billing email address: billing@cloudflare.com

Install

Terminal window
npm install @pqsafe/agent-pay @langchain/anthropic langchain
  1. Extract invoice amount from email (LLM step)

    The agent reads the email body and uses Claude to extract the invoice total.

    import { ChatAnthropic } from '@langchain/anthropic'
    import { HumanMessage } from '@langchain/core/messages'
    const llm = new ChatAnthropic({ model: 'claude-haiku-4-5' })
    async function extractInvoiceAmount(emailBody: string): Promise<number> {
    const response = await llm.invoke([
    new HumanMessage(
    `Extract the total invoice amount in USD from this Cloudflare billing email.
    Return ONLY a number (e.g. 47.20). No currency symbol, no text.
    Email:
    ${emailBody}`
    ),
    ])
    const amount = parseFloat(response.content as string)
    if (isNaN(amount) || amount <= 0) throw new Error(`Could not parse amount: ${response.content}`)
    return amount
    }
  2. Validate against envelope’s allowed recipients

    The envelope allowlists Cloudflare’s payment identifiers — the agent cannot accidentally pay the wrong recipient.

    import {
    generateKeyPair,
    createSpendEnvelope,
    createSignedEnvelope,
    } from '@pqsafe/agent-pay'
    const { secretKey } = await generateKeyPair()
    const envelope = createSpendEnvelope({
    agentId: 'devops-billing-agent',
    maxAmount: 500, // Cap: won't pay Cloudflare invoices over $500 autonomously
    currency: 'USD',
    allowedRails: ['wise', 'stripe'],
    allowedRecipients: [
    'cloudflare.com', // Wise international wire
    'cloudflare.com/billing', // Stripe card-on-file
    ],
    validUntil: new Date(Date.now() + 7 * 86_400_000), // 7 days
    requireApproval: true,
    approvalThreshold: 200, // Auto-approve under $200; Telegram approval over $200
    memo: 'Cloudflare infrastructure billing',
    })
    const signedEnvelope = createSignedEnvelope(envelope, secretKey)
  3. Dispatch via Wise (international wire) or Stripe (card-on-file)

    The agent picks the best available rail based on invoice currency.

    import { executeAgentPayment, buildLedgerRecord, submitToLedger } from '@pqsafe/agent-pay'
    async function payCloudflareInvoice(
    amount: number,
    invoiceRef: string,
    emailCurrency: 'USD' | 'EUR' | 'GBP' = 'USD'
    ) {
    // Use Wise for non-USD (international), Stripe for USD card-on-file
    const rail = emailCurrency !== 'USD' ? 'wise' : 'stripe'
    const result = await executeAgentPayment(signedEnvelope, {
    recipient: rail === 'stripe' ? 'cloudflare.com/billing' : 'cloudflare.com',
    amount,
    rail,
    memo: `Cloudflare invoice ${invoiceRef}${emailCurrency} ${amount}`,
    railOptions: rail === 'wise'
    ? { targetCurrency: emailCurrency, reference: invoiceRef }
    : { paymentMethodId: process.env.CLOUDFLARE_STRIPE_PM_ID },
    })
    await submitToLedger(buildLedgerRecord(signedEnvelope, result))
    return result
    }
  4. Log to ledger and notify Slack

    async function notifySlack(amount: number, status: string, txId: string) {
    await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
    text: [
    `*Cloudflare Invoice Paid* :white_check_mark:`,
    `Amount: $${amount}`,
    `Status: ${status}`,
    `TX ID: \`${txId}\``,
    `Authorized by: PQSafe ML-DSA-65 envelope`,
    ].join('\n'),
    }),
    })
    }
  5. Full MCP-compatible implementation

    This function can be registered as an MCP tool so any Claude agent can trigger it.

    import { Server } from '@modelcontextprotocol/sdk/server/index.js'
    import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
    const server = new Server(
    { name: 'pqsafe-billing-agent', version: '1.0.0' },
    { capabilities: { tools: {} } }
    )
    server.setRequestHandler('tools/list', async () => ({
    tools: [{
    name: 'pay_cloudflare_invoice',
    description: 'Pay a Cloudflare invoice from email. Extracts amount, validates envelope, dispatches payment.',
    inputSchema: {
    type: 'object',
    properties: {
    emailBody: { type: 'string', description: 'Raw Cloudflare invoice email body' },
    invoiceRef: { type: 'string', description: 'Invoice reference number' },
    },
    required: ['emailBody', 'invoiceRef'],
    },
    }],
    }))
    server.setRequestHandler('tools/call', async (req) => {
    const { emailBody, invoiceRef } = req.params.arguments as any
    const amount = await extractInvoiceAmount(emailBody)
    const result = await payCloudflareInvoice(amount, invoiceRef)
    await notifySlack(amount, result.status, result.txId)
    return {
    content: [{
    type: 'text',
    text: `Invoice paid. Amount: $${amount}. Status: ${result.status}. TX: ${result.txId}`,
    }],
    }
    })
    const transport = new StdioServerTransport()
    await server.connect(transport)

Expected output

Email detected: "Your Cloudflare invoice is ready — $47.20"
LLM extraction: $47.20
Rail selected: stripe (USD)
Envelope valid: true
Payment dispatched...
Payment status: settled
TX ID: ch_3abc123def
Slack notified: ✅ Cloudflare Invoice Paid — $47.20
Ledger hash: 0x7f3a...

Troubleshooting

ProblemSolution
LLM extracts wrong amountAdd invoice examples to the extraction prompt; validate 0 < amount < 500
APPROVAL_REQUIREDInvoice exceeds approvalThreshold — approve via Telegram or increase threshold
RECIPIENT_NOT_ALLOWEDCheck allowedRecipients matches the recipient passed to executeAgentPayment
Stripe card declinedCheck CLOUDFLARE_STRIPE_PM_ID is a valid stored payment method
Wise transfer failedVerify WISE_PROFILE_ID has USD source balance

Next steps