Skip to main content

Notification Channels

Notification channels send approval requests to external platforms. Approvers can approve or reject directly from their email inbox, Slack workspace, Telegram chat, or WhatsApp conversation.

How It Works

Payment triggers approval workflow
  → Conto generates secure, one-time action tokens
    → Notifications sent to all configured channels
      → Approver clicks Approve/Reject
        → Token is validated and consumed
          → Same approval logic as the dashboard
Every external decision uses the same submitApprovalDecision() path. Audit logs, atomic counters, webhook delivery, and race-condition safety all apply regardless of channel.

Supported Channels

ChannelDeliveryHow Approvers Act
EmailRich HTML via ResendClick Approve/Reject button links
SlackBlock Kit message with buttonsClick interactive action buttons
TelegramMessage with inline keyboardTap inline keyboard buttons
WhatsAppInteractive button messageTap reply buttons
WebhookJSON POST with tokensPOST tokens back to Conto API

Setting Up Channels

Go to Settings > Channels in the Conto dashboard, or use the REST API.
1

Choose a channel type

Click Add Channel and select the platform: Email, Slack, Telegram, WhatsApp, or Webhook.
2

Configure credentials

Each channel type requires different configuration:
ChannelRequired Config
EmailComma-separated recipient email addresses
SlackBot token (xoxb-...) and channel ID
TelegramBot token, chat ID, and optional webhook secret token
WhatsAppPhone number ID, access token, recipient numbers
WebhookHTTPS URL to receive JSON payloads
Webhook targets must resolve to public addresses. Conto rejects loopback, private, link-local, and other internal-only destinations. In production, webhook channels must use HTTPS.
3

Select event types

Choose which events trigger notifications:
EventWhen It FiresNotes
approval.requestedNew payment needs approvalAvailable on all channel types
approval.decidedSomeone approved or rejectedAvailable on all channel types
approval.escalatedRequest escalated after timeoutAvailable on all channel types
approval.expiredRequest expired without resolutionAvailable on all channel types
payment.executedPayment is submitted onchainWEBHOOK channels only
4

Test the channel

Click the test button to send a sample notification and verify your configuration.

Channel Configuration Details

Uses Resend to deliver rich HTML emails. Each eligible approver receives their own email with unique Approve and Reject buttons.Config fields:
  • recipients - List of email addresses. Only addresses matching eligible approvers receive actionable emails.
Clicking a button opens a browser, validates the token, submits the decision, and shows a confirmation page.
Requires a Slack app with the chat:write bot scope. Messages use Block Kit with payment details and interactive Approve/Reject buttons.Config fields:
  • botToken - Your Slack app’s bot token (xoxb-...)
  • channelId - The Slack channel ID to post messages to
Setup:
  1. Create a Slack app at api.slack.com/apps
  2. Add the chat:write bot scope
  3. Install the app to your workspace
  4. Set the interactivity request URL to https://conto.finance/api/webhooks/slack
  5. Set SLACK_SIGNING_SECRET in your Conto environment
When an approver clicks a button, Slack sends the interaction to Conto. The message is updated to show the result.
Uses the Telegram Bot API to send messages with inline keyboard buttons.Config fields:
  • botToken - Your Telegram bot token from @BotFather
  • chatId - The chat or group ID to send messages to
  • secretToken - Optional webhook secret. When set, Conto requires Telegram to send the matching x-telegram-bot-api-secret-token header on every callback.
Setup:
  1. Create a bot via @BotFather
  2. Set the webhook URL: https://api.telegram.org/bot{token}/setWebhook?url=https://conto.finance/api/webhooks/telegram
  3. If you configure secretToken, include Telegram’s secret_token option when calling setWebhook
  4. Add the bot to your group chat
Conto only accepts interactive callbacks for the configured chatId. If secretToken is set, Telegram callbacks must also include the matching secret header before approval tokens are processed.When an approver taps a button, the message updates to show the result.
Uses the WhatsApp Cloud API to send interactive button messages.Config fields:
  • phoneNumberId - Your WhatsApp Business phone number ID
  • accessToken - Permanent access token from Meta
  • recipientNumbers - Phone numbers in international format (e.g., +1234567890)
Setup:
  1. Register at developers.facebook.com
  2. Create a WhatsApp Business app
  3. Set the webhook URL to https://conto.finance/api/webhooks/whatsapp
  4. Set WHATSAPP_APP_SECRET and WHATSAPP_VERIFY_TOKEN in your Conto environment
  5. Subscribe to the messages webhook field
Sends a signed JSON payload to any HTTPS endpoint. Use this for custom integrations that need to review and submit approval decisions.Config fields:
  • url - Your HTTPS endpoint URL
Validation rules:
  • The destination must resolve to a public IP address
  • Private, loopback, link-local, and .internal / .local hosts are rejected
  • In production, only https:// webhook targets are accepted
Payload format:
{
  "event": "approval.requested",
  "approvalRequestId": "clxyz123...",
  "paymentDetails": {
    "amount": 500,
    "currency": "USDC",
    "recipientAddress": "0x5678...",
    "agentName": "Treasury Agent"
  },
  "tokens": {
    "approve": "base64url-token",
    "reject": "base64url-token"
  },
  "actionUrl": "https://conto.finance/api/approvals/action",
  "timestamp": "2026-04-06T12:00:00Z"
}
To submit a decision, POST the token back to the actionUrl:
curl -X POST https://conto.finance/api/approvals/action \
  -H "X-Action-Token: base64url-token" \
  -H "Content-Type: application/json" \
  -d '{"comment": "Approved via external review system"}'

REST API

Manage channels programmatically:
# List channels
curl https://conto.finance/api/notification-channels \
  -H "Authorization: Bearer $API_KEY"

# Create a channel
curl -X POST https://conto.finance/api/notification-channels \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channelType": "SLACK",
    "name": "Treasury Channel",
    "config": { "botToken": "xoxb-...", "channelId": "C012345" },
    "eventTypes": ["approval.requested", "approval.decided", "payment.executed"]
  }'

# Test a channel
curl -X POST https://conto.finance/api/notification-channels/{id}/test \
  -H "Authorization: Bearer $API_KEY"

# Delete a channel
curl -X DELETE https://conto.finance/api/notification-channels/{id} \
  -H "Authorization: Bearer $API_KEY"

Action Token Security

Action tokens are the core security mechanism for external approvals.
  • 32 bytes of cryptographic randomness, base64url-encoded
  • Protected at rest. Plaintext tokens are not retained after issuance
  • One-time use. Tokens cannot be reused after a decision is recorded
  • Time-limited. Tokens expire when the approval request expires (default 24 hours)
  • Per-user, per-action. Each approver gets separate Approve and Reject tokens
  • Full audit trail. Every decision records the channel, token ID, IP address, and user agent

Environment Variables

VariableRequired ForDescription
RESEND_API_KEYEmailResend API key for sending emails
SLACK_SIGNING_SECRETSlackSlack app signing secret for webhook verification
WHATSAPP_APP_SECRETWhatsAppMeta app secret for webhook signature verification
WHATSAPP_VERIFY_TOKENWhatsAppToken for Meta webhook verification challenge

Webhook Payload Validation

Inbound webhook payloads from Slack, Telegram, and WhatsApp are validated against typed Zod schemas before processing. Malformed payloads are rejected with a 400 response and never reach handler logic. Signature verification (HMAC) still runs first for all three providers regardless of payload shape.

Delta Verification Channel (pilot)

For organizations enrolled in the Delta Mandate pilot (Organization.deltaPilotEnabled = true), a WEBHOOK channel can be marked as a Delta channel via its config to receive a richer, machine-friendly payload built for automated verification. A Delta channel is a normal WEBHOOK channel with two extra fields in its config JSON:
{
  "url": "https://delta.example/webhooks/conto",
  "secret": "<hmac-secret>",
  "delta": true,
  "deltaApproverUserId": "<userId of the delta-verifier system user>"
}
When approval.requested fires for a Delta-flagged channel, the outbound payload includes invoice context from the typed PaymentRequest invoice record when present, with a fallback to legacy context.invoice data for older requests. It also includes the counterparty record and a pair of pre-minted single-use action tokens so the verifier can call back immediately:
{
  "event": "approval.requested",
  "approvalRequestId": "ar_...",
  "paymentDetails": {
    "paymentRequestId": "pr_...",
    "amount": 12345,
    "currency": "USDC",
    "recipientAddress": "0x...",
    "senderAddress": "0x...",
    "chainId": "42431",
    "category": "invoice",
    "purpose": "vendor payment",
    "memo": null,
    "executionMode": "PLATFORM",
    "walletId": "w_...",
    "agentName": "Acme AP Agent",
    "context": {
      /* full PaymentRequest.context */
    },
    "metadata": {
      /* lifted from context.metadata */
    },
    "createdAt": "...",
    "expiresAt": "...",
    "counterparty": {
      "id": "cp_...",
      "name": "Vendor LLC",
      "address": "0x...",
      "trustLevel": "TRUSTED",
      "approvalStatus": "APPROVED"
    }
  },
  "invoice": {
    "vendorId": "...",
    "vendorAddress": "0x...",
    "invoiceId": "INV-1042",
    "invoiceHash": "...",
    "invoiceSourceUrl": "https://...",
    "invoicePayload": {
      /* raw invoice */
    },
    "expectedAmount": "12345",
    "currency": "USDC",
    "dueDate": "2026-06-01"
  },
  "actionToken": {
    "approveToken": "<base64url, single-use>",
    "rejectToken": "<base64url, single-use>",
    "expiresAt": "...",
    "callbackUrl": "https://app.example/api/approvals/action"
  },
  "dashboardUrl": "https://app.example/dashboard?approvalRequestId=ar_...",
  "timestamp": "..."
}
HMAC signing is identical to the standard webhook channel (X-Conto-Signature: sha256(${X-Conto-Timestamp}.${rawBody})). For SDK-created payments, Conto now opens the approval request as part of the same flow when a matching workflow exists, so approval.requested webhooks are emitted without requiring a separate dashboard step. The SDK response also returns the approvalRequestId for traceability during testing. The verifier calls back into the existing action endpoint to record its decision and (optionally) attach a proof reference:
POST /api/approvals/action
Content-Type: application/json

{
  "token": "<approveToken or rejectToken>",
  "comment": "Verified against template invoice_payment_guard",
  "verification": {
    "proofType": "delta_signed",
    "proofRef": "https://delta.example/proofs/abc",
    "templateId": "...",
    "intentUuid": "...",
    "evidenceHash": "...",
    "reason": "OK"
  }
}
Proof material is persisted on the DeltaVerification row joined to the PaymentRequest, and surfaces on the Delta tab in the dashboard. For new SDK-created requests, Conto also stores the invoice fields from context.invoice in a typed PaymentRequest invoice relation. That typed record is what Delta webhook delivery now prefers when building approval and payment.executed payloads. Non-Delta WEBHOOK channels keep the standard payload shape — this enrichment is fully opt-in via the delta: true config flag. For production verifiers, prefer the signed internal callback route:
POST /api/internal/approval-requests/{approvalRequestId}/decide
X-Conto-Timestamp: 2026-05-26T18:00:00Z
X-Conto-Signature: <hex HMAC-SHA256 of `${timestamp}.${rawBody}` using config.secret>
Content-Type: application/json

{
  "decision": "APPROVED",
  "comment": "Verified against template invoice_payment_guard",
  "verification": {
    "proofType": "delta_signed",
    "proofRef": "https://delta.example/proofs/abc",
    "templateId": "...",
    "intentUuid": "...",
    "evidenceHash": "...",
    "reason": "OK"
  }
}
The actionToken.callbackUrl in the webhook payload remains the backwards- compatible demo path. Both routes persist the same verification {} block. If the same Delta channel also subscribes to payment.executed, Conto sends an execution webhook after the payment is submitted onchain so the verifier can mark its invoice row PAID without polling:
{
  "event": "payment.executed",
  "paymentDetails": {
    "paymentRequestId": "pr_...",
    "amount": 12345,
    "currency": "USDC",
    "recipientAddress": "0x...",
    "senderAddress": "0x...",
    "chainId": "42431",
    "category": "invoice",
    "purpose": "vendor payment",
    "memo": null,
    "executionMode": "PLATFORM",
    "walletId": "w_...",
    "agentName": "Acme AP Agent",
    "context": {
      /* full PaymentRequest.context */
    },
    "metadata": {
      /* lifted from context.metadata */
    },
    "createdAt": "...",
    "expiresAt": "...",
    "counterparty": {
      "id": "cp_...",
      "name": "Vendor LLC",
      "address": "0x...",
      "trustLevel": "TRUSTED",
      "approvalStatus": "APPROVED"
    }
  },
  "invoice": {
    "vendorId": "...",
    "vendorAddress": "0x...",
    "invoiceId": "INV-1042",
    "invoiceHash": "...",
    "invoiceSourceUrl": "https://...",
    "invoicePayload": {
      /* raw invoice */
    },
    "expectedAmount": "12345",
    "currency": "USDC",
    "dueDate": "2026-06-01"
  },
  "transaction": {
    "transactionId": "tx_...",
    "transactionHash": "0xabc123",
    "explorerUrl": "https://tempo.example/tx/0xabc123",
    "executedAt": "2026-05-26T18:45:00.000Z"
  },
  "timestamp": "..."
}

Next Steps

Approval Workflows

Configure multi-approval workflows with escalation and sequential approvals

Securing Agents

Set up spending limits and approval thresholds for your agents