Skip to main content

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. For the channel API reference (endpoints, request shapes), see Notification channels.

Prerequisites

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

Create the workflow

Go to Policies in the dashboard. Create a new Approval Workflow with:
FieldExample Value
NameLarge Payment Review
Amount Threshold$100
Required Approvals1
Timeout24 hours
Approver RolesOWNER, ADMIN
2

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 2: Add a Notification Channel

1

Go to Settings > Channels

Click Add Channel and choose your platform.
2

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
3

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
4

Test it

Click the test button. You should receive a sample notification on your platform.

Step 3: Test the Full Flow

1

Trigger a payment that needs approval

Use the SDK or dashboard to request a payment above your approval threshold.
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" }'
2

Check your channel

Within a few seconds, you should receive a notification on your configured channel with payment details and Approve/Reject buttons.
3

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

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

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

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.
# 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:
{
  "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 on the approval decision and the associated DeltaVerification record when the consumed token was issued for the WEBHOOK channel. This is the seam used by the Delta Mandate pilot to return its proof reference alongside the APPROVE/REJECT decision.
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 automated verifiers, prefer the HMAC-authenticated internal decision endpoint instead of the token callback. It uses the same secret configured on the Delta webhook channel and signs the exact string ${X-Conto-Timestamp}.${rawBody} with HMAC-SHA256.
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

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

Next Steps

Channel Reference

Full configuration reference for all channel types

Policy Testing

Test your approval workflows before going to production