Skip to content

Add a Telegram Approval Gate

Scenario: Your AI agent must pause and request human approval via Telegram for any payment over $50. If the human doesn’t respond within the envelope’s validUntil window, the payment expires automatically. Fully implemented using PQSafe’s Sprint 2 approval gate.

Prerequisites

  • @pqsafe/agent-pay installed
  • A Telegram bot (create via @BotFather — takes 30 seconds)
  • Your Telegram chat ID (send a message to your bot; use /getme to find it)
  • Environment variables:
    TELEGRAM_BOT_TOKEN=1234567890:ABCdef...
    TELEGRAM_CHAT_ID=987654321

Install

Terminal window
npm install @pqsafe/agent-pay node-telegram-bot-api
  1. Set requireApproval: true in envelope

    The envelope’s approvalThreshold means:

    • Payments ≤ threshold: auto-approved, no human needed
    • Payments > threshold: paused until human approval received
    import {
    generateKeyPair,
    createSpendEnvelope,
    createSignedEnvelope,
    } from '@pqsafe/agent-pay'
    const { secretKey } = await generateKeyPair()
    const envelope = createSpendEnvelope({
    agentId: 'autonomous-payment-agent',
    maxAmount: 500,
    currency: 'USD',
    allowedRails: ['airwallex', 'stripe'],
    allowedRecipients: ['vendor.com', 'anthropic.com', 'cloudflare.com'],
    validUntil: new Date(Date.now() + 4 * 3600_000), // 4-hour window for approval
    requireApproval: true,
    approvalThreshold: 50, // Require human approval for payments over $50
    memo: 'Autonomous agent — approval gate enabled',
    })
    const signedEnvelope = createSignedEnvelope(envelope, secretKey)
  2. Wire Telegram Bot webhook to PQSafe approval endpoint

    PQSafe’s executeWithApproval() handles the full approval dance: submits payment for approval, sends a Telegram message, waits for response.

    import { executeWithApproval, getTelegramChatId } from '@pqsafe/agent-pay'
    import TelegramBot from 'node-telegram-bot-api'
    const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN!, { polling: true })
    // Register the PQSafe approval handler with your Telegram bot
    const approvalConfig = {
    type: 'telegram' as const,
    botToken: process.env.TELEGRAM_BOT_TOKEN!,
    chatId: process.env.TELEGRAM_CHAT_ID!,
    messageTemplate: (amount: number, recipient: string, memo: string) =>
    `💳 *Payment Approval Required*\n\nAmount: *$${amount}*\nTo: ${recipient}\nMemo: ${memo}\n\nReply /approve or /deny`,
    }
    // The bot listens for approval responses
    bot.onText(/\/approve/, async (msg) => {
    const pendingApproval = await getPendingApproval(msg.chat.id.toString())
    if (pendingApproval) {
    await pendingApproval.approve()
    bot.sendMessage(msg.chat.id, '✅ Payment approved. Processing...')
    }
    })
    bot.onText(/\/deny/, async (msg) => {
    const pendingApproval = await getPendingApproval(msg.chat.id.toString())
    if (pendingApproval) {
    await pendingApproval.deny()
    bot.sendMessage(msg.chat.id, '❌ Payment denied.')
    }
    })
  3. Approval flow: agent submits → Telegram message → approve → dispatch resumes

    import { executeAgentPayment, buildLedgerRecord, submitToLedger } from '@pqsafe/agent-pay'
    async function payWithApproval(
    recipient: string,
    amount: number,
    memo: string
    ) {
    console.log(`Submitting payment request: $${amount} to ${recipient}`)
    const result = await executeWithApproval(
    signedEnvelope,
    {
    recipient,
    amount,
    memo,
    },
    approvalConfig // Telegram config from step 2
    )
    if (result.status === 'approved') {
    console.log('✅ Approved. Dispatching payment...')
    const paymentResult = await executeAgentPayment(signedEnvelope, { recipient, amount, memo })
    await submitToLedger(buildLedgerRecord(signedEnvelope, paymentResult))
    return paymentResult
    } else if (result.status === 'denied') {
    console.log('❌ Payment denied by human operator.')
    return { status: 'denied' }
    } else if (result.status === 'expired') {
    console.log('⏰ Approval window expired.')
    return { status: 'expired' }
    }
    }
  4. Timeout handling (envelope expires if not approved within validUntil)

    If the human doesn’t approve before the envelope expires, executeWithApproval rejects with ENVELOPE_EXPIRED. No payment is ever made without approval.

    import { PQSafeError } from '@pqsafe/agent-pay'
    try {
    const result = await payWithApproval('vendor.com/billing', 150, 'SaaS subscription')
    console.log('Final status:', result?.status)
    } catch (err) {
    if (err instanceof PQSafeError && err.code === 'ENVELOPE_EXPIRED') {
    console.log('Payment cancelled — approval window closed without response.')
    // Issue a new envelope if still needed
    } else {
    throw err
    }
    }
  5. Full approval gate implementation

    import {
    generateKeyPair,
    createSpendEnvelope,
    createSignedEnvelope,
    executeWithApproval,
    executeAgentPayment,
    buildLedgerRecord,
    submitToLedger,
    PQSafeError,
    } from '@pqsafe/agent-pay'
    import TelegramBot from 'node-telegram-bot-api'
    const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN!, { polling: true })
    const { secretKey } = await generateKeyPair()
    const signedEnvelope = createSignedEnvelope(
    createSpendEnvelope({
    agentId: 'approval-gate-demo',
    maxAmount: 500,
    currency: 'USD',
    allowedRails: ['airwallex'],
    allowedRecipients: ['vendor.com'],
    validUntil: new Date(Date.now() + 4 * 3600_000),
    requireApproval: true,
    approvalThreshold: 50,
    }),
    secretKey
    )
    const approvalConfig = {
    type: 'telegram' as const,
    botToken: process.env.TELEGRAM_BOT_TOKEN!,
    chatId: process.env.TELEGRAM_CHAT_ID!,
    }
    // Large payment — will request approval
    try {
    const result = await executeWithApproval(
    signedEnvelope,
    { recipient: 'vendor.com/invoice', amount: 150, memo: 'Q2 services' },
    approvalConfig
    )
    console.log('Approval result:', result.status)
    } catch (err) {
    if (err instanceof PQSafeError) {
    console.error('PQSafe error:', err.code, err.message)
    }
    }

What the Telegram message looks like

💳 Payment Approval Required
Amount: $150
To: vendor.com/invoice
Memo: Q2 services
Envelope: env_01J4X... (expires in 3h 47m)
Reply /approve or /deny

Expected output

Submitting payment request: $150 to vendor.com/invoice
[Telegram message sent to chat 987654321]
[Human replies: /approve]
✅ Approved. Dispatching payment...
Payment status: settled
TX ID: txn_airwallex_abc123
Ledger hash: 0x7f3a...
Final status: settled

Troubleshooting

ProblemSolution
Telegram bot doesn’t respondCheck TELEGRAM_BOT_TOKEN is correct; verify bot is running (polling: true)
ENVELOPE_EXPIREDApproval window closed — increase validUntil duration in envelope
Payment executes without approvalCheck requireApproval: true in envelope; verify approvalThreshold is set
Bot receives message but approval failsEnsure getPendingApproval() is correctly linked to the pending approval state
Human approved but payment not dispatchedexecuteWithApproval returns; check your code calls executeAgentPayment after approval

Next steps