Skip to main content

Notification Channels

Notification channels send approval requests and webhook lifecycle events to external platforms. Approvers can approve or reject directly from their email inbox, Slack workspace, Telegram chat, or WhatsApp conversation. Webhook channels can also receive machine-readable payment execution and Conto Pay request events for agents and back-office systems.

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 and a signing secret (secret in config)
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
conto_pay.request.receivedA hosted Conto Pay request is sent to the payerWEBHOOK channels only
conto_pay.request.paidA hosted Conto Pay request is paidWEBHOOK channels only
conto_pay.request.rejectedA hosted Conto Pay request is rejectedWEBHOOK channels only
conto_pay.request.expiredA hosted Conto Pay request expiresWEBHOOK 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
  • secret - Signing secret for HMAC verification (required; signingSecret and webhookSecret are accepted aliases)
Every delivery is signed: the X-Conto-Signature header carries an HMAC-SHA256 of ${timestamp}.${rawBody} computed with your channel secret. Verify it before trusting the payload.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"}'

Conto Pay Lifecycle Webhooks

Webhook channels can subscribe to hosted Conto Pay request lifecycle events: conto_pay.request.received, conto_pay.request.paid, conto_pay.request.rejected, and conto_pay.request.expired. The received event is delivered to the payer organization. Terminal outcomes (paid, rejected, and expired) are delivered to both the payer and payee organizations when they have matching active webhook channels. Each Conto Pay webhook payload includes absolute hosted links for the request review page and the payer action page. These match the copyable links shown in the Conto Pay activity inbox and hosted request review screens, so external systems and human operators can refer to the same URL.
{
  "event": "conto_pay.request.paid",
  "request": {
    "id": "ipr_...",
    "status": "PAID",
    "amount": 250,
    "currency": "pathUSD",
    "purpose": "Invoice INV-100",
    "invoiceId": "INV-100",
    "dueDate": "2026-06-30T00:00:00.000Z",
    "expiresAt": "2026-07-01T00:00:00.000Z",
    "createdAt": "2026-06-14T12:00:00.000Z",
    "updatedAt": "2026-06-14T12:05:00.000Z",
    "direction": "incoming"
  },
  "payer": {
    "agentId": "agent_payer",
    "agentName": "Buyer AP Agent",
    "agentSlug": "accounts-payable",
    "organizationId": "org_buyer",
    "organizationName": "Buyer Inc",
    "organizationSlug": "buyer-inc",
    "handle": "@buyer-inc/accounts-payable"
  },
  "payee": {
    "agentId": "agent_payee",
    "agentName": "Vendor AR Agent",
    "agentSlug": "accounts-receivable",
    "organizationId": "org_vendor",
    "organizationName": "Vendor LLC",
    "organizationSlug": "vendor-llc",
    "handle": "@vendor-llc/accounts-receivable"
  },
  "links": {
    "requestUrl": "https://conto.finance/pay-requests/ipr_...",
    "payerActionUrl": "https://conto.finance/conto-pay?request=ipr_..."
  },
  "recipientOrganizationId": "org_buyer",
  "transaction": {
    "id": "tx_...",
    "txHash": "0xabc123",
    "status": "CONFIRMED"
  },
  "timestamp": "2026-06-14T12:05:00.000Z"
}
Use request.direction to render the event from the receiving organization’s point of view: incoming means the organization is the payer, while outgoing means the organization created the request and is waiting on the payer.

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)

Delta is a guided pilot that routes payment approvals through an external invoice-verification service. It is not a self-serve channel type: Conto activates it for your organization during onboarding as a standard WEBHOOK channel with additional Delta configuration. For access, onboarding inputs, and activation details, see Delta Verification Setup; to validate the live flow end to end, see Delta Smoke Test. Two things differ from a standard webhook channel: 1. Enriched payloads. When approval.requested fires for a Delta-enabled channel, the outbound payload carries the full payment context in paymentDetails (paymentRequestId, senderAddress, chainId, category, memo, executionMode, walletId, context, metadata, and timestamps, in addition to the standard fields) plus 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 pilot verification service can call back immediately. Abbreviated:
{
  "event": "approval.requested",
  "approvalRequestId": "ar_...",
  "paymentDetails": {
    /* full payment context, plus: */
    "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": "..."
}
If the same Delta channel also subscribes to payment.executed, Conto sends an execution webhook after the payment is submitted onchain, with the same invoice enrichment plus a transaction block (transactionId, transactionHash, explorerUrl, executedAt), so the verifier can mark its invoice row PAID without polling. Non-Delta WEBHOOK channels keep the standard payload shape; this enrichment only appears for organizations that Conto has enabled for Delta. HMAC signing is identical to the standard webhook channel (X-Conto-Signature: sha256(${X-Conto-Timestamp}.${rawBody})). 2. Signed decision callback. For production verification services, prefer the signed callback route to record the decision and (optionally) attach a proof reference:
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: POST the approveToken or rejectToken to /api/approvals/action with the same optional verification {} block. Both routes persist the same verification data, and proof material surfaces on the Delta tab in the dashboard once your organization has Delta enabled.

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