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-payinstalled- A Telegram bot (create via @BotFather — takes 30 seconds)
- Your Telegram chat ID (send a message to your bot; use
/getmeto find it) - Environment variables:
TELEGRAM_BOT_TOKEN=1234567890:ABCdef...TELEGRAM_CHAT_ID=987654321
Install
npm install @pqsafe/agent-pay node-telegram-bot-api-
Set
requireApproval: truein envelopeThe envelope’s
approvalThresholdmeans:- 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 approvalrequireApproval: true,approvalThreshold: 50, // Require human approval for payments over $50memo: 'Autonomous agent — approval gate enabled',})const signedEnvelope = createSignedEnvelope(envelope, secretKey) -
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 botconst 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 responsesbot.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.')}}) -
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' }}} -
Timeout handling (envelope expires if not approved within
validUntil)If the human doesn’t approve before the envelope expires,
executeWithApprovalrejects withENVELOPE_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}} -
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 approvaltry {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: $150To: vendor.com/invoiceMemo: Q2 servicesEnvelope: env_01J4X... (expires in 3h 47m)
Reply /approve or /denyExpected output
Submitting payment request: $150 to vendor.com/invoice[Telegram message sent to chat 987654321][Human replies: /approve]✅ Approved. Dispatching payment...Payment status: settledTX ID: txn_airwallex_abc123Ledger hash: 0x7f3a...Final status: settledTroubleshooting
| Problem | Solution |
|---|---|
| Telegram bot doesn’t respond | Check TELEGRAM_BOT_TOKEN is correct; verify bot is running (polling: true) |
ENVELOPE_EXPIRED | Approval window closed — increase validUntil duration in envelope |
| Payment executes without approval | Check requireApproval: true in envelope; verify approvalThreshold is set |
| Bot receives message but approval fails | Ensure getPendingApproval() is correctly linked to the pending approval state |
| Human approved but payment not dispatched | executeWithApproval returns; check your code calls executeAgentPayment after approval |
Next steps
- Pay Anthropic Credits — combine with auto-topup for credit management
- Approval concept — deep dive on the approval gate mechanics
- Wrap an AP2 Mandate — recurring mandate payments with approval gates