Skip to main content
You can provide a webhook URL when creating a Scout via the Create Scout endpoint. The scout_webhook format is a structured JSON format that provides nested data. If you want Slack-compatible payloads, provide a Slack incoming webhook URL and set webhook_format to slack when creating or updating the scout. Slack payloads use Block Kit formatting.

Example Request

Below is an example of the HTTP request that a Scout sends to your webhook endpoint.
POST https://your-webhook-endpoint.com/webhook
Content-Type: application/json
User-Agent: Scout-Webhook/1.0
X-Scout-Event: scout.update
{
  "event_type": "scout_update",
  "scout": {
    "id": "scout-123",
    "display_name": "Reddit Posts Monitor",
    "query": "Posts on r/sanfrancisco about new offices opening in downtown sf"
  },
  "update": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": "2024-08-05T15:30:45.123456Z",
    "status": "completed",
    "has_changes": true,
    "summary": "Found 3 new posts about office openings in downtown SF",
    "details_url": "https://scouts.yutori.com/scout-123",
    "report_content": "..."
  },
  "delivery": {
    "id": "550e8400-e29b-41d4-a716-446655440001",
    "attempt": 1,
    "timestamp": "2024-08-05T15:30:45.123456Z"
  }
}

Slack Incoming Webhook Payload (Example)

{
  "text": "Scout 'Reddit Posts Monitor' has new updates",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Scout Update: Reddit Posts Monitor",
        "emoji": true
      }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Query:*\nPosts on r/sanfrancisco about new offices opening in downtown sf" },
        { "type": "mrkdwn", "text": "*Status:*\nHas New Updates" }
      ]
    }
  ]
}

Field Descriptions

FieldDescription
event_typeAlways "scout_update"
scout.idUnique identifier for the Scout
scout.display_nameHuman-readable name
scout.queryOriginal query/task description
update.idUnique identifier for this update
update.timestampISO 8601 timestamp when update was generated
update.statusTypically "completed"
update.has_changesBoolean indicating if new content was found
update.summaryBrief description of what was found
update.details_urlURL to view full results
update.report_contentRaw content of the update
delivery.idUnique identifier for this delivery attempt
delivery.attemptDelivery attempt number (starts at 1)
delivery.timestampTimestamp when webhook was sent

Webhook Delivery Details

Headers

All webhook requests include the following headers:
  • Content-Type: application/json
  • User-Agent: Scout-Webhook/1.0
  • X-Scout-Event: scout.update

Response Expectations

Your webhook endpoint should:
  • Respond with HTTP status 200-299 for successful receipt
  • Process the webhook asynchronously if needed
  • Respond within 10 seconds (default timeout)

Error Handling

Scout will consider delivery failed if:
  • HTTP response status is not 2xx
  • Request times out (default 10 seconds)
  • Network error occurs
Failed deliveries are logged but not automatically retried.

Security Considerations

  • Use HTTPS URLs for production webhooks
  • HTTP URLs are only allowed for localhost/127.0.0.1 (testing)
  • Validate webhook payload structure in your endpoint
  • Consider implementing webhook signature verification for additional security

Smoke Test

You can send a test webhook from the API using your API key:
curl --request POST \
  --url https://api.yutori.com/v1/scouting/webhooks/test \
  --header 'X-API-KEY: YOUR_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
    "webhook_url": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
    "webhook_format": "slack"
  }'

Example Integration Code

Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def handle_scout_webhook():
    payload = request.get_json()

    # Handle scout_webhook format
    if payload.get('event_type') == 'scout_update':
        scout_name = payload['scout']['display_name']
        has_changes = payload['update']['has_changes']
        summary = payload['update']['summary']

        print(f"Scout '{scout_name}' update: {summary}")

    return jsonify({'status': 'received'}), 200

Node.js (Express)

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
    const payload = req.body;

    // Handle scout_webhook format
    if (payload.event_type === 'scout_update') {
        const scoutName = payload.scout.display_name;
        const hasChanges = payload.update.has_changes;
        const summary = payload.update.summary;

        console.log(`Scout '${scoutName}' update: ${summary}`);
    }
    res.status(200).json({ status: 'received' });
});

app.listen(3000, () => {
    console.log('Webhook server listening on port 3000');
});