> ## Documentation Index
> Fetch the complete documentation index at: https://conto.finance/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Notification Channels

> Route approval, governance, payment execution, and Conto Pay lifecycle events to email, Slack, Telegram, WhatsApp, and custom webhooks

# 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
governance approval events for agents, compliance tools, 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 follows the same approval rules as a dashboard decision and appears in the
same audit history. When a decision reaches the required approval count, Conto submits payments from
managed wallets. The resulting transaction hash and explorer URL are available from the approval
review; `payment.executed` remains the lifecycle event for downstream webhook consumers. External
and smart-contract wallets still use their manual execution flow.

When a policy requires approval and no custom workflow matches, Conto creates a one-review request
for an organization Owner, Admin, or Manager. The dashboard alert and configured notification
channels reference the same reviewable request.

## General Store Email Actions

General Store emails use the same hashed action-token infrastructure for customer order approval,
merchant Accept and Ready actions, customer status links, and disabling Store auto-pay. Order
actions are scoped to the order and intended recipient, expire automatically, and are consumed
atomically when used so a replay cannot repeat a lifecycle transition.

Stripe Connect onboarding uses a separate merchant-scoped, time-limited renewal token. It does not
contain Stripe credentials and cannot edit a merchant or enable payouts. Its only authority is to
request a fresh single-use Stripe Account Link for the same merchant when an emailed onboarding
link expires. Stripe `account.updated` events and an explicit admin sync remain the source of truth
for charges and payout readiness.

## Supported Channels

| Channel  | Delivery                       | How Approvers Act                 |
| -------- | ------------------------------ | --------------------------------- |
| Email    | Rich HTML via Resend           | Click Approve/Reject button links |
| Slack    | Block Kit message with buttons | Click interactive action buttons  |
| Telegram | Message with inline keyboard   | Tap inline keyboard buttons       |
| WhatsApp | Interactive button message     | Tap reply buttons                 |
| Webhook  | JSON POST with tokens          | POST tokens back to Conto API     |

## Setting Up Channels

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

<Steps>
  <Step title="Choose a channel type">
    Click **Add Channel** and select the platform: Email, Slack, Telegram, WhatsApp, or Webhook.
  </Step>

  <Step title="Configure credentials">
    Each channel type requires different configuration:

    | Channel  | Required Config                                     |
    | -------- | --------------------------------------------------- |
    | Email    | Comma-separated recipient email addresses           |
    | Slack    | Bot token (`xoxb-...`) and channel ID               |
    | Telegram | Bot token, chat ID, and webhook secret token        |
    | WhatsApp | Phone number ID, access token, recipient numbers    |
    | Webhook  | HTTPS 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.
  </Step>

  <Step title="Select event types">
    Choose which events trigger notifications:

    | Event                           | When It Fires                                       | Notes                          |
    | ------------------------------- | --------------------------------------------------- | ------------------------------ |
    | `approval.requested`            | New payment needs approval                          | Available on all channel types |
    | `approval.decided`              | Someone approved or rejected                        | Available on all channel types |
    | `approval.escalated`            | Request escalated after timeout                     | Available on all channel types |
    | `approval.expired`              | Request expired without resolution                  | Available on all channel types |
    | `governance.approval.requested` | Protected governance action needs review            | `WEBHOOK` channels only        |
    | `governance.approval.decided`   | Protected governance action is approved or rejected | `WEBHOOK` channels only        |
    | `payment.executed`              | Payment is submitted onchain                        | `WEBHOOK` channels only        |
    | `conto_pay.request.received`    | A hosted Conto Pay request is sent to the payer     | `WEBHOOK` channels only        |
    | `conto_pay.request.paid`        | A hosted Conto Pay request is paid                  | `WEBHOOK` channels only        |
    | `conto_pay.request.rejected`    | A hosted Conto Pay request is rejected              | `WEBHOOK` channels only        |
    | `conto_pay.request.expired`     | A hosted Conto Pay request expires                  | `WEBHOOK` channels only        |
  </Step>

  <Step title="Test the channel">
    Click the test button to send a sample notification and verify your configuration.
  </Step>
</Steps>

## Channel Configuration Details

<AccordionGroup>
  <Accordion title="Email">
    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.
  </Accordion>

  <Accordion title="Slack">
    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](https://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.
  </Accordion>

  <Accordion title="Telegram">
    Uses the Telegram Bot API to send messages with inline keyboard buttons.

    **Config fields:**

    * `botToken` - Your Telegram bot token from [@BotFather](https://t.me/BotFather)
    * `chatId` - The chat or group ID to send messages to
    * `secretToken` - Required webhook secret. 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. 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`.
    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.
  </Accordion>

  <Accordion title="WhatsApp">
    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](https://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
  </Accordion>

  <Accordion title="Webhook">
    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)

    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:**

    ```json theme={null}
    {
      "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`:

    ```bash theme={null}
    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"}'
    ```
  </Accordion>
</AccordionGroup>

## 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.

```json theme={null}
{
  "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": {
    "agentName": "Buyer AP Agent",
    "agentSlug": "accounts-payable",
    "organizationName": "Buyer Inc",
    "organizationSlug": "buyer-inc",
    "handle": "@buyer-inc/accounts-payable"
  },
  "payee": {
    "agentName": "Vendor AR Agent",
    "agentSlug": "accounts-receivable",
    "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_..."
  },
  "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.

## Governance Approval Webhooks

Webhook channels can subscribe to governance approval events for protected organization changes:
`governance.approval.requested` and `governance.approval.decided`.

Use these events when an external audit, compliance, or ticketing system needs a durable record of
sensitive configuration changes such as policy updates, policy rule changes, API key lifecycle
actions, or freeze configuration changes. Governance approval webhooks are notification-only. Owners
and admins still approve or reject requests from the Conto dashboard.

For dashboard setup, protected-action selection, and the in-product review flow, see
[Governance Approvals](/docs/guides/governance-approvals).

```json theme={null}
{
  "event": "governance.approval.decided",
  "approvalRequest": {
    "id": "gov_approval_...",
    "action": "POLICY_UPDATE",
    "resource": "policy",
    "resourceId": "policy_...",
    "status": "APPROVED",
    "reason": "Update policy High value spend",
    "requestPayload": "{\"isActive\":false}",
    "requestedByActorType": "USER",
    "requestedByActorId": "user_requester",
    "requestedByUserId": "user_requester",
    "expiresAt": "2026-07-14T00:00:00.000Z",
    "approvedUntil": "2026-07-13T22:15:00.000Z",
    "decidedAt": "2026-07-13T22:00:00.000Z",
    "decidedByUserId": "user_admin",
    "decisionComment": "Reviewed with security policy owner",
    "createdAt": "2026-07-13T21:55:00.000Z",
    "updatedAt": "2026-07-13T22:00:00.000Z"
  },
  "requestedBy": {
    "id": "user_requester",
    "name": "Requester",
    "email": "requester@example.com"
  },
  "decidedBy": {
    "id": "user_admin",
    "name": "Admin",
    "email": "admin@example.com"
  },
  "organizationId": "org_...",
  "timestamp": "2026-07-13T22:00:00.000Z"
}
```

## REST API

Manage channels programmatically:

```bash theme={null}
# 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"]
  }'

# Create a governance approval webhook channel
curl -X POST https://conto.finance/api/notification-channels \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channelType": "WEBHOOK",
    "name": "Governance Audit Stream",
    "config": { "url": "https://example.com/conto/webhooks", "secret": "whsec_..." },
    "eventTypes": ["governance.approval.requested", "governance.approval.decided"]
  }'

# 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

Store order approval emails use the same one-time token model. When a connected agent starts a
General Store checkout that requires customer approval, Conto emails the customer order-bound
Approve and Decline links. Approval opens hosted checkout for that order; decline cancels the
approval-pending order and releases the reserved spend.

## 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 decision callbacks

Organizations using Delta receive a signed `approval.requested` webhook with the approval
reference and the payment and invoice fields required for verification. Verify the
`X-Conto-Signature` over `${X-Conto-Timestamp}.${rawBody}` before acting on the request. See
[Delta Verification Setup](/docs/guides/delta-setup) and the
[Delta End-to-End Test](/docs/guides/delta-smoke-test) for the complete integration sequence.

After verification, submit the decision to the decision callback URL provisioned during Delta
onboarding. Use the approval reference from the webhook:

```bash theme={null}
POST {decisionCallbackUrl}
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": "Invoice and recipient verified",
  "verification": {
    "proofType": "delta_signed",
    "proofRef": "https://delta.example/proofs/abc",
    "evidenceHash": "...",
    "reason": "OK"
  }
}
```

The response returns `recorded`, `idempotent`, the approval and payment references, a customer
status, and status URLs. A pending response includes an `await_more_approvals` next action. Retrying
the same signed decision is safe; a conflicting decision returns `DECISION_CONFLICT`.

Subscribe to `payment.executed` when the verifier also needs the settlement transaction reference,
hash, explorer URL, and execution time for reconciliation.

## Next Steps

<CardGroup cols={2}>
  <Card title="Approval Workflows" icon="check-double" href="/docs/policies/advanced">
    Configure multi-approval workflows with escalation and sequential approvals
  </Card>

  <Card title="Securing Agents" icon="shield" href="/docs/guides/securing-agents">
    Set up spending limits and approval thresholds for your agents
  </Card>
</CardGroup>
