Skip to content

Configuring Alerts

Alerts allow your trap to notify you when specific conditions are met. When an operator executes your trap's shouldAlert function and it returns true, an alert is sent to your configured notification channels (Slack, or webhooks).

How Alerts Work

  1. Operator Execution: Operators execute your trap's shouldAlert function on every new block
  2. Alert Condition: If shouldAlert returns true, the operator sends alert data to the Drosera alert server
  3. Notification Delivery: The alert server forwards the alert to your configured destinations (Slack channels, webhooks)
  4. Alert Management: Alerts can be created, updated, and deleted without making any on chain changes.

Implementing shouldAlert in Your Trap

Your trap contract must implement the shouldAlert function which receives an array of collected data and returns a boolean along with custom alert data:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
 
import {Trap} from "drosera-contracts/Trap.sol";
 
struct CollectOutput {
    uint256 liquidity;
    uint256 blockNumber;
}
 
struct AlertOutput {
    bool shouldAlert;
    bytes alertData;
}
 
contract MyTrap is Trap {
    function collect() external view override returns (bytes memory) {
        // Collect data from the blockchain
        return abi.encode(CollectOutput({
            liquidity: getLiquidity(),
            blockNumber: block.number
        }));
    }
 
    function shouldAlert(bytes[] calldata data) external pure override returns (bool, bytes memory) {
        if (data.length == 0) {
            return (false, bytes(""));
        }
 
        // Decode the collected data
        CollectOutput memory latestData = decodeAlertOutput(data[data.length - 1]);
        
        // Define your alert condition
        bool conditionMet = latestData.liquidity < 1000 ether;
        
        if (conditionMet) {
            // Return true and encode alert data
            bytes memory alertData = abi.encode(latestData);
            return (true, alertData);
        }
        
        return (false, bytes(""));
    }
    
    // Helper function to decode alert output in your trap
    // You don't have to use this in your trap, it's essentially 
    // used to know how to decode the `alertData` bytes returned by 
    // `shouldAlert` in the alert server. You can use any function name you want.
    function decodeAlertOutput(bytes calldata data) public pure returns (CollectOutput memory) {
        return abi.decode(data, (CollectOutput));
    }
    
    function getLiquidity() internal view returns (uint256) {
        // Your liquidity logic here
        return 1000 ether;
    }
}

Key Points

  • shouldAlert receives an array of collected data (from your collect function)
  • Return (true, alertData) when you want to trigger an alert
  • Return (false, bytes("")) when no alert is needed
  • The alertData will be decoded and sent to your configured channels

Configuring Alerts in drosera.toml

Alerts are configured under the [traps.<trap_name>.alert] section in your drosera.toml:

[traps.my_trap]
path = "out/MyTrap.sol/MyTrap.json"
cooldown_period_blocks = 32
block_sample_size = 10
 
[traps.my_trap.alert]
title = "Low Liquidity Alert"
severity = "high"
enabled = true
labels = { "pool" = "ETH/USD", "description" = "Liquidity below threshold" }
alert_output_sig = "decodeAlertOutput"
webhook = { webhook_url = "https://your-server.com/webhook" }

Alert Configuration Fields

Required Fields

  • title (string): A descriptive title for your alert
  • severity (string): One of low, medium, high, critical
  • enabled (boolean): Whether the alert is active

Optional Fields

  • labels (map): Key-value pairs for categorizing alerts

    labels = { "environment" = "production", "severity" = "critical" }
  • alert_output_sig (string): The function name used to decode alert data

    • Example: "decodeAlertOutput"
    • Must match a function in your contract that can decode the alertData returned by shouldAlert
  • users (array): User addresses that can view and manage alerts in the Drosera web app https://app.drosera.io/alerts. Needed for configuring Slack alerts.

    users = [
      { address = "0x1234..." },
      { address = "0x5678..." },
    ]
  • slack (object): Slack channel configuration

    slack = { slack_channel = "#drosera-alerts" }
  • webhook (object): Webhook endpoint configuration

    webhook = { 
      webhook_url = "https://your-server.com/webhook",
      webhook_headers = { "Authorization" = "Bearer token" }
    }

Severity Levels

Severity levels help categorize and prioritize alerts:

  • low: Information that doesn't require immediate action
  • medium: Important information that should be reviewed
  • high: Issues that need prompt attention
  • critical: Urgent issues requiring immediate action

The severity is displayed in Slack alerts with emoji indicators (👋 ⚠️ ❗ 🚨) and included in webhook payloads.

Setting Up Slack Alerts

1. Configure in drosera.toml

[traps.my_trap.alert]
title = "Low Liquidity Alert"
severity = "high"
enabled = true
alert_output_sig = "decodeAlertOutput"
users = [
  { address = "0x1234567890123456789012345678901234567890" }
]
slack = { slack_channel = "#drosera-alerts" }

2. Deploy your trap

drosera apply

This will deploy your trap and create the alert configuration in the alert server.

3. Install Slack App

You need to install the Drosera Slack app in your workspace and authorize the alert server to post messages:

  1. Navigate to https://app.drosera.io/alerts
  2. Connect the wallet that owns the configured user address.
  3. Follow the instructions to authenticate with the Drosera app.
  4. Click on the "Install Slack App" button on the associated alert.
  5. Follow the instructions to install the app in your workspace and select the channel you want to send alerts to.

slack app installation

After installing the app, you will be redirected to the alerts page with the app installed.

slack app installed

4. Slack Alert Format

When an alert is triggered, Slack will receive a message with:

  • Header: Alert title with severity emoji
  • Trap Address: The contract address of your trap
  • Chain: Network name (Ethereum, Localnet, etc.)
  • Block Number: Block where the alert was triggered
  • Timestamp: When the alert was sent (UTC)
  • Severity: Alert severity level
  • Labels: Optional key-value pairs
  • Output Data: Decoded alert data from your shouldAlert function

Example Slack message: slack alert message

Setting Up Webhook Alerts

Webhooks allow you to send alert data to any HTTP endpoint. This is useful for integrating with external monitoring systems, databases, or custom alert handlers.

1. Configure Webhook in drosera.toml

[traps.my_trap.alert]
title = "Low Liquidity Alert"
severity = "high"
labels = { "pool" = "ETH/USD", "description" = "Liquidity below threshold" }
alert_output_sig = "decodeAlertOutput"
enabled = true
webhook = { 
  webhook_url = "https://your-server.com/api/alerts",
  webhook_headers = { "Authorization" = "Bearer your-token" }
}

2. Deploy your trap

drosera apply

This will deploy your trap and create the alert configuration in the alert server.

3. Webhook Payload

Your webhook endpoint will receive a POST request with the following JSON payload:

{
  "trap_address": "0x1234567890123456789012345678901234567890",
  "block_number": 12345678,
  "owner": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd",
  "title": "Low Liquidity Alert",
  "severity": "high",
  "chain_id": 1,
  "labels": {
    "pool": "ETH/USD",
    "description": "Liquidity below threshold"
  },
  "response_data": {
    "liquidity": "500000000000000000",
    "blockNumber": "12345678"
  }
}

3. Creating a Webhook Handler

Example webhook handler in Node.js:

const express = require('express');
const app = express();
 
app.use(express.json());
 
app.post('/api/alerts', (req, res) => {
  const {
    trap_address,
    block_number,
    title,
    severity,
    chain_id,
    labels,
    response_data
  } = req.body;
  
  console.log(`Alert: ${title}`);
  console.log(`Severity: ${severity}`);
  console.log(`Trap: ${trap_address}`);
  console.log(`Block: ${block_number}`);
  console.log(`Labels:`, labels);
  console.log(`Data:`, response_data);
  
  // Your alert handling logic here
  // - Index the alert in your monitoring system
  // - Send an email to the alert owner
  // - Trigger an automation
  // - etc.
  
  res.status(200).json({ received: true });
});
 
app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Webhook Security

For production webhooks, you should:

  1. Verify the sender: Use signature verification to ensure requests are from the alert server
  2. Use HTTPS: Always use https:// URLs
  3. Validate data: Check that the payload matches your expected structure
  4. Rate limit: Implement rate limiting to prevent abuse

Example with signature verification:

webhook = { 
  webhook_url = "https://your-server.com/api/alerts",
  webhook_headers = { 
    "X-API-Key" = "your-secret-key",
    "Authorization" = "Bearer {{env.WEBHOOK_SECRET_KEY}}" # if using drosera.toml.j2
  }
}

Understanding Alert Output Signatures

The alert_output_sig field tells the alert server how to decode the alertData bytes returned by your shouldAlert function.

Simple Types

For a function returning a single uint256:

// In your trap contract
function decodeAlertOutput(bytes calldata data) public pure returns (uint256) {
    return abi.decode(data, (uint256));
}

Output JSON:

"500000000000000000"

Structs

For returning custom structs:

struct CollectOutput {
    uint256 liquidity;
    uint256 blockNumber;
}
 
function decodeAlertOutput(bytes calldata data) public pure returns (CollectOutput memory) {
    return abi.decode(data, (CollectOutput));
}

Output JSON:

{
  "liquidity": "500000000000000000",
  "blockNumber": "12345678"
}

Multiple Parameters

For returning multiple values:

function decodeAlertOutput(bytes calldata data) public pure returns (uint256, address) {
    return abi.decode(data, (uint256, address));
}

Output JSON:

["500000000000000000", "0x1234567890123456789012345678901234567890"]

Labeling Alerts

Labels are key-value pairs that help categorize and filter alerts:

labels = { 
  "environment" = "production",
  "component" = "liquidity-monitor",
  "pool" = "ETH/USD",
  "description" = "Liquidity below minimum threshold"
}

Labels are included in:

  • Slack messages
  • Webhook payloads
  • Alert queries

You can use labels to:

  • Group related alerts
  • Filter alerts in monitoring systems
  • Add context to alert notifications
  • Enable alert routing based on tags

Enabling and Disabling Alerts

Alerts can be enabled or disabled by modifying the drosera.toml file:

# Enable alerts
[traps.my_trap.alert]
enabled = true
 
# Disable alerts
[traps.my_trap.alert]
enabled = false

When enabled = false:

  • The trap continues to run normally
  • The alert server won't send alert data to the alert server
  • No notifications will be sent

When enabled = true:

  • Operators will call shouldAlert and send alert data when it returns true
  • Notifications will be sent to configured channels

After modifying the drosera.toml file, you need to run the drosera apply command to apply the changes.

Multi-Channel Alerting

You can configure multiple notification channels for a single alert:

[traps.my_trap.alert]
title = "Critical System Alert"
severity = "critical"
enabled = true
labels = { "severity" = "critical" }
alert_output_sig = "decodeAlertOutput"
 
# Send to Slack
slack = { slack_channel = "#critical-alerts" }
 
# Send to webhook
webhook = { webhook_url = "https://your-server.com/alerts" }

All configured channels will receive the alert simultaneously.

Testing Alerts

Dry Run

Use the dry run command to test your trap's alert logic without actually sending notifications:

drosera dryrun --trap-name my_trap

This will show you:

  • Whether shouldAlert returns true or false
  • What data would be sent if an alert was triggered
  • How the alert data would be decoded

Test Alert

Once your trap is deployed and configured, you can send a test alert:

drosera send-test-alert --trap-name my_trap

This sends a test notification to verify your Slack or webhook configuration is working correctly.

Managing Alert Configuration

Creating Alerts

When you deploy a new trap with alert configuration:

drosera apply

This will:

  1. Deploy your trap contract
  2. Create the alert configuration in the alert server
  3. Link users, Slack channels, and webhooks to the alert

Updating Alerts

To update an existing alert configuration:

  1. Modify the alert section in your drosera.toml:

    [traps.my_trap.alert]
    title = "Updated Alert Title"  # Changed
    severity = "critical"        # Changed
    enabled = true
  2. Apply the changes:

    drosera apply

Deleting Alerts

To remove an alert configuration:

  1. Remove the alert section from drosera.toml:

    # [traps.my_trap.alert]  # Commented out or deleted
  2. Apply the changes:

    drosera apply

Best Practices

Alert Conditions

  • Keep alert conditions specific and actionable
  • Avoid alerting on temporary or expected conditions
function shouldAlert(bytes[] calldata data) external pure override returns (bool, bytes memory) {
    if (data.length < 2) {
        return (false, bytes(""));
    }
    
    CollectOutput memory previous = abi.decode(data[data.length - 2], (CollectOutput));
    CollectOutput memory current = abi.decode(data[data.length - 1], (CollectOutput));
    
    // Only alert if condition persists across multiple blocks
    bool shouldTrigger = current.liquidity < threshold && previous.liquidity < threshold;
    
    if (shouldTrigger) {
        return (true, abi.encode(current));
    }
    
    return (false, bytes(""));
}

Severity Levels

  • Use appropriate severity levels for the impact of the condition
  • Reserve critical for truly urgent issues
  • Use low for informational alerts

Labels

  • Use consistent label keys across related traps
  • Include relevant context (pool names, contract addresses, etc.)
  • Keep label values descriptive but concise

Webhook Reliability

  • Implement retry logic in your webhook handler
  • Use idempotency keys to handle duplicate alerts
  • Log all alert payloads for debugging

Troubleshooting

Alert Not Triggering

  1. Check enabled: Ensure enabled = true in your config
  2. Verify shouldAlert logic: Use dry run to test the condition
  3. Check operator status: Ensure operators are running and watching your trap
  4. Review trap configuration: Confirm the trap is deployed correctly

Slack Not Receiving Alerts

  1. Check Slack app installation: Ensure the Drosera app is installed
  2. Verify channel name: Use the exact channel name including # (private channels do not use #)

Webhook Not Receiving Alerts

  1. Test endpoint: Verify your webhook URL is accessible
  2. Check headers: Ensure required headers are configured correctly
  3. Review firewall rules: Confirm the alert server can reach your endpoint
  4. Validate SSL certificate: For HTTPS endpoints, check certificate validity

Incorrect Decoded Data

  1. Verify alert_output_sig: Ensure it matches a public function in your contract that can decode the alertData bytes returned by shouldAlert
  2. Check struct encoding: Confirm the struct matches the encoded data
  3. Review encoding in contract: Verify how data is encoded in shouldAlert

Complete Example

Here's a complete example of a trap with alert configuration:

Trap Contract

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;
 
import {Trap} from "drosera-contracts/Trap.sol";
 
struct CollectOutput {
    uint256 balance;
    uint256 timestamp;
    uint256 blockNumber;
}
 
contract BalanceMonitor is Trap {
    uint256 public constant LOW_BALANCE_THRESHOLD = 1000 ether;
    
    function collect() external view override returns (bytes memory) {
        return abi.encode(CollectOutput({
            balance: address(this).balance,
            timestamp: block.timestamp,
            blockNumber: block.number
        }));
    }
    
    function shouldAlert(bytes[] calldata data) external pure override returns (bool, bytes memory) {
        if (data.length == 0) {
            return (false, bytes(""));
        }
        
        CollectOutput memory latest = decodeAlertOutput(data[data.length - 1]);
        
        if (latest.balance < LOW_BALANCE_THRESHOLD) {
            return (true, abi.encode(latest));
        }
        
        return (false, bytes(""));
    }
    
    function decodeAlertOutput(bytes calldata data) public pure returns (CollectOutput memory) {
        return abi.decode(data, (CollectOutput));
    }
}

Configuration

[traps.balance_monitor]
path = "out/BalanceMonitor.sol/BalanceMonitor.json"
block_sample_size = 5
 
[traps.balance_monitor.alert]
title = "Low Balance Warning"
severity = "high"
enabled = true
labels = { 
  "type" = "balance-monitor",
  "threshold" = "1000 ETH" 
}
users = [
  { address = "0x1234567890123456789012345678901234567890" }
]
alert_output_sig = "decodeAlertOutput"
 
# Multiple notification channels
slack = { slack_channel = "#finance-alerts" }
webhook = { 
  webhook_url = "https://your-server.com/alerts",
  webhook_headers = { "Authorization" = "Bearer secret-token" }
}

Deploy and Apply

# Deploy the trap
drosera apply
 
# Test the alert logic
drosera dryrun --trap-name balance_monitor
 
# Send a test alert
drosera send-test-alert --trap-name balance_monitor

This configuration will:

  1. Monitor the contract balance on every block
  2. Alert when balance drops below 1000 ETH
  3. Send notifications to Slack, webhook, and specified users
  4. Include decoded balance data in all alerts