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

# External Approval Channels

> Set up email, Slack, Telegram, and WhatsApp approval flows so your team can approve payments without logging into Conto

# External Approval Channels

This guide walks through setting up approval notifications so your team can approve or reject payments from wherever they already work.

This is the setup walkthrough. For the concepts (when to use approval workflows, trigger conditions, patterns), see [Approval workflows](/guides/approval-workflows). For the channel API reference (endpoints, request shapes), see [Notification channels](/integrations/notification-channels).

If you are using the pilot Delta verification flow specifically, see
[Delta Verification Setup](/guides/delta-setup) and
[Delta Smoke Test](/guides/delta-smoke-test). Delta access is currently enabled by Conto during
onboarding; if you want access, contact [sales@conto.finance](mailto:sales@conto.finance).

## Prerequisites

<Check>An active organization with at least one agent</Check>
<Check>An approval workflow configured ([Securing Agents](/guides/securing-agents))</Check>
<Check>Admin or Owner role in your organization</Check>

## What You'll Build

By the end of this guide, your approval flow will look like this:

```
Agent requests $500 payment
  → Approval workflow triggers
    → Approver gets a Slack message with Approve/Reject buttons
    → Approver clicks "Approve" in Slack
      → Payment is approved and executed
      → Audit log records: who, when, which channel
```

## Step 1: Set Up an Approval Workflow

If you haven't already, create an approval workflow that triggers on the conditions you care about.

<Steps>
  <Step title="Create the workflow">
    Go to **Policies** in the dashboard. Create a new Approval Workflow with:

    | Field              | Example Value        |
    | ------------------ | -------------------- |
    | Name               | Large Payment Review |
    | Amount Threshold   | \$100                |
    | Required Approvals | 1                    |
    | Timeout            | 24 hours             |
    | Approver Roles     | OWNER, ADMIN         |
  </Step>

  <Step title="Verify it triggers">
    Request a payment above your threshold and confirm it enters `PENDING_APPROVAL` status.
    SDK payment requests and external-wallet `/api/sdk/payments/approve` calls with a matching
    workflow also return an `approvalRequestId`, which is handy when you are testing external
    verifier flows end to end.
  </Step>
</Steps>

## Step 2: Add a Notification Channel

<Steps>
  <Step title="Go to Settings > Channels">
    Click **Add Channel** and choose your platform.
  </Step>

  <Step title="Configure the channel">
    Fill in the platform-specific fields.

    **For Slack:**

    * Bot Token: `xoxb-...` (from your Slack app)
    * Channel ID: `C0123456789`

    **For Email:**

    * Recipients: `cfo@company.com, finance@company.com`

    **For Telegram:**

    * Bot Token: from @BotFather
    * Chat ID: your group chat ID
  </Step>

  <Step title="Select events">
    At minimum, enable `approval.requested`. Consider also enabling:

    * `approval.escalated` for time-sensitive visibility
    * `approval.expired` so nothing falls through the cracks
    * `payment.executed` on `WEBHOOK` channels if your verifier needs to mark invoices as paid
  </Step>

  <Step title="Test it">
    Click the test button. You should receive a sample notification on your platform.
  </Step>
</Steps>

## Step 3: Test the Full Flow

<Steps>
  <Step title="Trigger a payment that needs approval">
    Use the SDK or dashboard to request a payment above your approval threshold.

    ```bash theme={null}
    curl -X POST https://conto.finance/api/sdk/payments/request \
      -H "Authorization: Bearer $SDK_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "amount": 500, "recipientAddress": "0x...", "purpose": "Test approval" }'
    ```
  </Step>

  <Step title="Check your channel">
    Within a few seconds, you should receive a notification on your configured channel with payment details and Approve/Reject buttons.
  </Step>

  <Step title="Approve or reject">
    Complete the approval from the email confirmation page or the Conto dashboard. The payment should move to `APPROVED` status and execute automatically if you are using Conto-managed wallets.
  </Step>

  <Step title="Verify the audit log">
    Go to **Audit Logs** in the dashboard. You should see the approval decision with the channel recorded (e.g., `SLACK`, `EMAIL`).
  </Step>
</Steps>

## Multiple Channels

You can configure multiple channels simultaneously. When an approval request is created, notifications are sent to all active channels that subscribe to the `approval.requested` event. The first approver to act from any channel resolves the request.

<Info>
  Each channel delivers independently via background jobs. If one channel fails (e.g., Slack API is
  down), other channels still deliver. Failed deliveries retry up to 3 times with exponential
  backoff.
</Info>

## Webhook Integration for Custom Systems

If you already have your own approval system, use the Webhook channel type to receive approval requests and hand the final decision off to an authenticated Conto approver.

```bash theme={null}
# 1. Configure a webhook channel pointing to your system
# 2. Your system receives a signed payload with `eligibleApprovers` and `dashboardUrl`
# 3. Notify the right approver or open your own review UI
# 4. Send the user to `dashboardUrl` to complete the approval in Conto
```

The response confirms the decision:

```json theme={null}
{
  "success": true,
  "message": "Payment request approved.",
  "finalStatus": "APPROVED",
  "channel": "WEBHOOK"
}
```

### Attaching verification metadata

Programmatic approvers can optionally attach a `verification {}` block to the
decision call. Conto persists it alongside the approval decision when the
consumed token was issued for a `WEBHOOK` channel. The Delta verification
pilot uses this to return its proof reference alongside the APPROVE/REJECT
decision.

```bash theme={null}
curl -X POST https://app.example/api/approvals/action \
  -H "Content-Type: application/json" \
  -d '{
    "token": "<approveToken>",
    "comment": "Verified against invoice_payment_guard",
    "verification": {
      "proofType": "delta_signed",
      "proofRef": "https://delta.example/proofs/abc",
      "templateId": "...",
      "intentUuid": "...",
      "evidenceHash": "...",
      "reason": "OK"
    }
  }'
```

The `verification` block is strict: only the documented keys are accepted;
unknown keys cause a `400`. `proofType` accepts `delta_signed` (signed JSON
receipt) or `delta_mandate_sp1` (SP1 zk proof).

### Preferred service-to-service callback

For long-running pilot verification services, prefer the HMAC-authenticated internal
decision endpoint instead of the token callback. It uses the same `secret`
configured on the Delta pilot webhook channel and signs the exact string
`${X-Conto-Timestamp}.${rawBody}` with HMAC-SHA256.

```bash theme={null}
timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
body='{
  "decision": "APPROVED",
  "comment": "Verified against invoice_payment_guard",
  "verification": {
    "proofType": "delta_signed",
    "proofRef": "https://delta.example/proofs/abc",
    "templateId": "...",
    "intentUuid": "...",
    "evidenceHash": "...",
    "reason": "OK"
  }
}'
signature="$(printf '%s.%s' "$timestamp" "$body" | \
  openssl dgst -sha256 -hmac "$DELTA_WEBHOOK_SECRET" -hex | sed 's/^.* //')"

curl -X POST https://app.example/api/internal/approval-requests/ar_123/decide \
  -H "Content-Type: application/json" \
  -H "X-Conto-Timestamp: $timestamp" \
  -H "X-Conto-Signature: $signature" \
  -d "$body"
```

This route is the long-term external-verifier contract. The token-based
`/api/approvals/action` flow remains available for demos, email-style click
throughs, and simple webhook integrations.

For SDK-driven payment requests and external-wallet `/api/sdk/payments/approve`
calls, Conto automatically opens the approval request when a workflow matches
and returns the resulting `approvalRequestId` in the initial API response. That
ID is the same one used in the callback route above.

## Troubleshooting

<AccordionGroup>
  <Accordion title="Notifications not arriving">
    1. Check that the channel is **active** (toggle is on) in Settings > Channels
    2. Check the `lastError` field on the channel for API errors
    3. Verify your bot token or credentials are correct
    4. For Slack, confirm the bot is invited to the target channel
    5. For Telegram, confirm the webhook URL is set correctly
  </Accordion>

  <Accordion title="Token expired or already used">
    Action tokens are one-time use and expire with the approval request. If the approval request has already been resolved (by another approver or from the dashboard), the token is no longer valid.
  </Accordion>

  <Accordion title="Slack buttons not working">
    Confirm your Slack app's interactivity request URL is set to `https://conto.finance/api/webhooks/slack` and that `SLACK_SIGNING_SECRET` is configured in your environment.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Channel Reference" icon="tower-broadcast" href="/integrations/notification-channels">
    Full configuration reference for all channel types
  </Card>

  <Card title="Policy Testing" icon="flask-vial" href="/guides/policy-testing">
    Test your approval workflows before going to production
  </Card>
</CardGroup>
