Skip to main content

Overview

Webhooks notify your server when agent tasks complete. Instead of polling for results, configure a URL and Stewrd will POST the response to your endpoint — signed with HMAC-SHA256 so you can verify authenticity. Webhooks are available on the Dev plan and above.

Setup

Configure webhooks in your project dashboard:
  1. Open your project settings
  2. In the Webhooks section, enter your endpoint URL (must be HTTPS)
  3. Save — your signing secret is displayed once, copy it immediately
  4. Use the Send Test button to verify delivery

Payload Format

When an agent task completes, Stewrd sends a POST request to your webhook URL:
{
  "event": "agent.completed",
  "id": "request-uuid",
  "object": "agent.response",
  "message": "The agent's response message...",
  "capabilities_used": ["chat", "research"],
  "files": [],
  "usage": {
    "tokens_used": 1247
  },
  "meta": {
    "duration_ms": 3400,
    "project_id": "project-uuid",
    "plan": "dev"
  }
}
For session messages, the payload includes "object": "session.message" and an additional session_id field. For requests with custom tools, the webhook fires once the agent reaches status: "completed" — after all tool call rounds are finished. Intermediate requires_tool_outputs responses do not trigger webhooks.

Headers

Every webhook request includes these headers:
HeaderDescription
Content-Typeapplication/json
X-Stewrd-SignatureHMAC signature: t=<timestamp>,v1=<hex>
X-Stewrd-Webhook-IdUnique delivery ID
X-Stewrd-Webhook-TimestampUnix timestamp of the delivery
User-AgentStewrd-Webhooks/1.0

Signature Verification

Always verify the X-Stewrd-Signature header to confirm the webhook came from Stewrd. The signature format is t=<timestamp>,v1=<hmac> where:
  • timestamp is the Unix timestamp when the webhook was sent
  • hmac is the HMAC-SHA256 of <timestamp>.<payload> using your signing secret
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const [tPart, vPart] = signature.split(',');
  const timestamp = tPart.split('=')[1];
  const expectedSig = vPart.split('=')[1];

  // Verify signature
  const signedContent = `${timestamp}.${payload}`;
  const hmac = crypto
    .createHmac('sha256', secret)
    .update(signedContent)
    .digest('hex');

  if (hmac !== expectedSig) {
    throw new Error('Invalid webhook signature');
  }

  // Optional: reject old timestamps (prevent replay attacks)
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp);
  if (age > 300) {
    throw new Error('Webhook timestamp too old');
  }

  return JSON.parse(payload);
}

// Express handler
app.post('/webhooks/stewrd', (req, res) => {
  const signature = req.headers['x-stewrd-signature'];
  const payload = req.body; // raw body string

  try {
    const event = verifyWebhook(payload, signature, process.env.STEWRD_WEBHOOK_SECRET);
    console.log('Agent completed:', event.id);
    res.sendStatus(200);
  } catch (err) {
    res.sendStatus(401);
  }
});

Retry Behavior

If your endpoint returns a non-2xx status code or is unreachable, Stewrd retries with exponential backoff:
AttemptDelay
1stImmediate
2nd30 seconds
3rd5 minutes
After 3 failed attempts, the delivery is marked as failed. Each delivery attempt has a 10-second timeout.

Delivery Log

View delivery history in your project dashboard under the Webhooks section. Each delivery shows:
  • Status: delivered, pending, or failed
  • HTTP response code from your endpoint
  • Attempt count
  • Timestamp

Managing Webhooks

Regenerate Signing Secret

If your signing secret is compromised, regenerate it from the dashboard. The old secret stops working immediately.

Disable / Enable

Toggle webhooks on/off without removing the configuration. Disabled webhooks skip delivery entirely.

Test Delivery

Send a test webhook with a sample payload to verify your endpoint is working. Test events use "event": "webhook.test".