Skip to main content
Webhooks let Placet push review responses to your server via HTTP callbacks. Your agent sends a message with a review and a webhookUrl, and Placet POSTs the response to that URL when a human responds. This is the recommended connection type for background automations, CI/CD pipelines, and serverless functions that can’t maintain persistent connections.

When to Use Webhooks

  • Your agent runs as a background service or serverless function
  • You don’t want to hold a connection open waiting for a response
  • Your agent has a publicly accessible HTTP endpoint
  • You need fire-and-forget with async response handling
If your agent can’t expose a public endpoint, use Long-Polling or MCP instead.

Setting a Webhook URL

Webhook URLs can be set at three levels, in order of priority:

1. Per-message (highest priority)

Set webhookUrl directly on the message:
curl -X POST "$PLACET_URL/api/v1/messages" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "your-agent-id",
    "text": "Approve deployment to production?",
    "webhookUrl": "https://your-server.com/webhook/deploy",
    "review": {
      "type": "approval",
      "payload": {
        "options": [
          { "id": "approve", "label": "Approve", "style": "primary" },
          { "id": "reject", "label": "Reject", "style": "danger" }
        ]
      }
    }
  }'

2. Per-channel (default for all messages)

Configure a default webhook URL on the channel/agent in the dashboard under Settings > Channels. This URL is used for all messages in that channel unless overridden by a per-message URL.

3. Legacy callback (lowest priority)

Set review.callback on the review object:
{
  "review": {
    "type": "approval",
    "payload": { "options": [{ "id": "ok", "label": "OK" }] },
    "callback": {
      "url": "https://your-server.com/webhook",
      "method": "POST",
      "headers": { "X-Custom-Header": "value" }
    }
  }
}
The first matching webhook URL wins. If a message has webhookUrl, the channel-level and callback URLs are ignored.

Webhook Payload

When a human responds, Placet POSTs a JSON payload to your webhook URL:
{
  "event": "review:responded",
  "channelId": "clxyz456",
  "message_id": "clxyz111",
  "review_type": "approval",
  "response": {
    "selectedOption": "approve",
    "comment": "Looks good, ship it!"
  },
  "responded_at": "2026-04-01T14:32:00.000Z"
}
The response shape depends on the review type:
Review TypeResponse Fields
approvalselectedOption, comment?
selectionselectedIds (string array)
formKey-value pairs matching field names
text-inputtext (string)
freeformArbitrary key-value pairs

Webhook Server Example

A minimal Express.js server that handles webhook callbacks:
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook/deploy', (req, res) => {
  const { event, response, message_id } = req.body;

  if (event === 'review:responded') {
    const { selectedOption, comment } = response;
    console.log(`Deployment ${selectedOption} (message: ${message_id})`);
    if (comment) console.log(`Comment: ${comment}`);

    if (selectedOption === 'approve') {
      // Trigger deployment pipeline
      triggerDeploy();
    }
  }

  res.sendStatus(200);
});

app.listen(8080, () => {
  console.log('Webhook server running on :8080');
});

Delivery Status

Placet tracks webhook delivery and exposes the status in the dashboard:
StatusMeaning
sentMessage stored, webhook not yet sent
webhook_deliveredYour endpoint returned 2xx
webhook_failedYour endpoint returned non-2xx or timed out
agent_receivedAgent explicitly acknowledged (via ACK endpoint)

Retrying Failed Deliveries

When a webhook delivery fails, users can retry from the dashboard UI. The retry re-sends the original payload.

Security

SSRF Protection

Placet validates all webhook URLs and blocks requests to private/internal IP ranges to prevent SSRF attacks:
  • 10.0.0.0/8
  • 172.16.0.0/12
  • 192.168.0.0/16
  • 127.0.0.0/8
  • ::1, fd00::/8
Your webhook endpoint must be publicly accessible. Placet will reject requests to private IP addresses and localhost URLs.

Timeouts

Webhook requests have a 10-second timeout. If your endpoint doesn’t respond within 10 seconds, the delivery is marked as webhook_failed.

Recommendations

  • Return 200 OK as quickly as possible — process the payload asynchronously
  • Implement idempotency using the message.id field to handle retries
  • Validate the payload structure before processing
  • Use HTTPS for your webhook endpoint in production