Add interactive buttons to your agent - let users confirm payments, choose options, or trigger custom actions.
What are Callbacks?
When your agent needs a user decision, it can show interactive buttons. When clicked, your custom code executes.
Example: Agent asks "Confirm payment of $99?" with [Confirm] and [Cancel] buttons. User clicks [Confirm] → your payment handler runs → payment processed.
Quick Start
1. Create a button in your workflow
typescriptimport { flutch } from '@flutch/sdk'; // Agent asks user to confirm const token = await flutch.callbacks.issue({ handler: 'confirm-payment', params: { amount: 99.99, orderId: 'abc123' } }); return { content: 'Confirm payment of $99.99?', buttons: [ { text: 'Confirm', callbackToken: token } ] };
2. Create callback handler class
typescript// src/callbacks/payment.callbacks.ts import { Injectable } from '@nestjs/common'; import { Callback, ExtendedCallbackContext, CallbackResult } from '@flutch/sdk'; @Injectable() export class PaymentCallbacks { @Callback('confirm-payment') async handleConfirmPayment( context: ExtendedCallbackContext ): Promise<CallbackResult> { const { amount, orderId } = context.params; // Your logic here await processPayment(orderId, amount); return { success: true, message: 'Payment confirmed!', patch: { text: '✓ Payment confirmed', disableButtons: true } }; } }
3. Register callbacks in builder
typescript// src/graph/versions/v1.0.0/builder.ts import { Injectable } from '@nestjs/common'; import { WithCallbacks, AbstractGraphBuilder } from '@flutch/sdk'; import { PaymentCallbacks } from '../../../callbacks/payment.callbacks'; @Injectable() @WithCallbacks(PaymentCallbacks) export class MyGraphV1Builder extends AbstractGraphBuilder<'1.0.0'> { readonly version = '1.0.0' as const; // builder implementation... }
Done! When user clicks [Confirm], your confirmPayment function runs.
Handler Details
What you receive (context)
typescript{ userId: string; // Who clicked threadId: string; // Conversation ID params: {...}; // Data from issue() platform: 'web' | 'telegram' | 'whatsapp'; }
What you return
typescript{ success: boolean; message?: string; // Optional success message error?: string; // If success: false patch?: {...}; // Update the UI (optional) }
UI Patches
Update the message after button click:
typescriptreturn { success: true, patch: { type: 'update-message', content: '✓ Payment confirmed', removeButtons: true } };
Example: Order Confirmation
typescript// In your workflow - show button const token = await flutch.callbacks.issue({ handler: 'confirm-order', params: { items: cart.items, total: 99.99 } }); return { content: `Total: $99.99. Confirm order?`, buttons: [{ text: 'Confirm', callbackToken: token }] }; // In your callback handler class @Injectable() export class OrderCallbacks { @Callback('confirm-order') async handleConfirmOrder( context: ExtendedCallbackContext ): Promise<CallbackResult> { const { params, userId } = context; // Your business logic const order = await createOrder(userId, params.items); await processPayment(order.id, params.total); // Return success + update UI return { success: true, patch: { text: `✓ Order #${order.id} confirmed!`, disableButtons: true } }; } }
Security
Flutch handles security automatically:
- Token expiration - Tokens expire after 10 minutes (configurable with
ttlSec) - User validation - Only the user who received the button can click it
- Rate limiting - Prevents spam clicks
- Idempotency - Duplicate clicks don't cause duplicate actions
- Auto-retry - Failed handlers retry automatically with backoff
Best Practices
1. Handle errors
typescript@Injectable() export class MyCallbacks { @Callback('process-action') async handleAction(context: ExtendedCallbackContext): Promise<CallbackResult> { try { await riskyOperation(); return { success: true }; } catch (error) { return { success: false, error: 'Please try again later' }; } } }
2. Keep handlers fast
typescript@Injectable() export class MyCallbacks { // Good - returns quickly @Callback('start-job') async handleStartJob(context: ExtendedCallbackContext): Promise<CallbackResult> { const jobId = await startBackgroundJob(context.params); return { success: true, data: { jobId } }; } // Bad - will timeout @Callback('slow-operation') async handleSlowOperation(context: ExtendedCallbackContext): Promise<CallbackResult> { await longOperation(); // ❌ Takes 30+ seconds return { success: true }; } }
Testing
bash# 1. Register and start flutch register npm run dev # 2. Test flutch test "I want to pay" # Click the button in console UI
View metrics in console: console.flutch.ai/agents/{agent-id}/callbacks
Next Steps
Learn more about Flutch development:
- Workflows - Build complex agent workflows
- Architecture Overview - Understand the platform
- Versioning - Manage graph versions
Build your interactive agent:
- Quick Start - Deploy your first graph
- CLI Reference - Command line tools