Skip to Content
Developer DocsActions Webhooks

Actions Webhooks

AgentPress supports incoming webhooks from external integrations to create pending actions for agent processing. This enables automation workflows where external systems (like review platforms, CRMs, or custom integrations) can trigger agent actions.

Overview

The webhook system uses Svix for HMAC signature verification, ensuring secure communication between external systems and AgentPress.

Endpoint Format: POST /webhooks/actions/:orgId/:webhookIdentifier

Setting Up Webhooks

Step 1: Create a Webhook Configuration

In the AgentPress Admin Console:

  1. Navigate to ActionsWebhooks tab
  2. Click Create Webhook
  3. Fill in the configuration:
FieldDescription
NameDisplay name for the webhook (e.g., “Local Falcon Reviews”)
Webhook IdentifierURL-safe identifier used in the endpoint (e.g., local_falcon)
Default Action RuleRule to use when processing incoming webhooks (optional)
  1. Click Create
  2. Important: Copy and save the webhook secret from the dialog - it’s only shown once!

Your webhook URL will be:

https://api.agent.press/webhooks/actions/{orgId}/{webhookIdentifier}

Step 2: Create an Action Rule

Before webhooks can create actions, you need at least one rule:

  1. Navigate to ActionsRules tab
  2. Click Create Rule
  3. Configure the rule:
FieldDescription
NameDisplay name for the rule
Action TypeType of action (e.g., review_response, email_reply)
AgentWhich agent handles these actions
InstructionsAdditional instructions appended to the agent prompt
Tools Requiring ApprovalTools that need human approval before execution
  1. Save the rule
  2. Link it to your webhook as the default rule

Webhook Payload Structure

All webhooks must send a JSON payload with the following structure:

interface WebhookPayload { // Event type identifier (e.g., "review.created", "email.received") eventType: string; // Unique ID from the external system (used for idempotency) externalId: string; // External user ID (resolved via auth provider if configured) userId: string; // Optional: Override the default action rule actionRuleId?: string; // Custom data specific to your integration data: { [key: string]: unknown; }; }

Example Payload

{ "eventType": "review.created", "externalId": "review-12345", "userId": "external-user-456", "data": { "reviewText": "Great service! The staff was super helpful.", "rating": 5, "locationName": "Downtown Location", "reviewerName": "Jane Smith", "reviewDate": "2025-01-16T17:30:00Z" } }

Required Headers

All webhook requests must include Svix signature headers:

HeaderDescription
svix-idUnique message ID (e.g., msg_abc123def456)
svix-timestampUnix timestamp in seconds
svix-signatureHMAC signature in format: v1,{base64_signature}

Sending Test Webhooks

# Install svix CLI npm install -g svix # Send a test webhook svix webhook test \ --url "http://localhost:3001/webhooks/actions/{YOUR_ORG_ID}/local_falcon" \ --secret "whsec_YOUR_WEBHOOK_SECRET" \ --data '{ "eventType": "review.created", "externalId": "review-123", "userId": "external-user-456", "data": { "reviewText": "Great service!", "rating": 5, "locationName": "Main Street Store", "reviewerName": "John Doe" } }'

Option B: Node.js Test Script

Create a file called test-webhook.js:

const crypto = require('crypto'); const ORG_ID = 'your-org-id-here'; const WEBHOOK_IDENTIFIER = 'local_falcon'; const SECRET = 'your-secret-here'; // Base64 part after whsec_ const API_URL = 'http://localhost:3001'; async function sendTestWebhook() { const msgId = 'msg_' + crypto.randomBytes(16).toString('hex'); const timestamp = Math.floor(Date.now() / 1000).toString(); const payload = { eventType: 'review.created', externalId: `review-${Date.now()}`, // Unique for idempotency userId: 'test-user-123', data: { reviewText: 'This place is amazing! The staff was super helpful.', rating: 5, locationName: 'Downtown Location', reviewerName: 'Jane Smith', reviewDate: new Date().toISOString() } }; const payloadString = JSON.stringify(payload); const toSign = `${msgId}.${timestamp}.${payloadString}`; const signature = crypto .createHmac('sha256', Buffer.from(SECRET, 'base64')) .update(toSign) .digest('base64'); const response = await fetch( `${API_URL}/webhooks/actions/${ORG_ID}/${WEBHOOK_IDENTIFIER}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'svix-id': msgId, 'svix-timestamp': timestamp, 'svix-signature': `v1,${signature}` }, body: payloadString } ); const result = await response.json(); console.log('Status:', response.status); console.log('Response:', result); } sendTestWebhook().catch(console.error);

Run with:

node test-webhook.js

Option C: cURL with Manual Signing

# Generate signature using Node.js node -e " const crypto = require('crypto'); const secret = 'YOUR_WEBHOOK_SECRET'; // without 'whsec_' prefix const msgId = 'msg_' + crypto.randomBytes(16).toString('hex'); const timestamp = Math.floor(Date.now() / 1000).toString(); const payload = JSON.stringify({ eventType: 'review.created', externalId: 'review-123', userId: 'external-user-456', data: { reviewText: 'Great service!', rating: 5, locationName: 'Main Street Store' } }); const toSign = msgId + '.' + timestamp + '.' + payload; const signature = crypto .createHmac('sha256', Buffer.from(secret, 'base64')) .update(toSign) .digest('base64'); console.log('svix-id:', msgId); console.log('svix-timestamp:', timestamp); console.log('svix-signature:', 'v1,' + signature); console.log('payload:', payload); " # Use the generated values in cURL curl -X POST "http://localhost:3001/webhooks/actions/{YOUR_ORG_ID}/local_falcon" \ -H "Content-Type: application/json" \ -H "svix-id: {GENERATED_MSG_ID}" \ -H "svix-timestamp: {GENERATED_TIMESTAMP}" \ -H "svix-signature: {GENERATED_SIGNATURE}" \ -d '{YOUR_PAYLOAD}'

Response Codes

CodeDescription
200Success - action created or already exists
401Missing or invalid signature headers
403Webhook is disabled
404Webhook configuration not found
429Rate limit exceeded (100 requests/minute by default)
500Internal server error

Success Response

{ "success": true, "actionId": "uuid-of-created-action" }

Idempotent Response (Action Already Exists)

{ "success": true, "actionId": "uuid-of-existing-action", "alreadyExists": true }

Skipped Response (No Rule Configured)

{ "success": true, "skipped": true, "reason": "No action rule configured" }

Verifying Actions Were Created

After sending a webhook:

  1. Navigate to ActionsPending tab in the console
  2. You should see your new action with status “Pending”
  3. Click on the action to view the source data

Local Development

Since webhooks require a public URL, use ngrok  for local development:

# Terminal 1: Start your API server bun dev # Terminal 2: Create a tunnel to your API port ngrok http 3001

Use the ngrok URL as your webhook endpoint:

https://abc123.ngrok.io/webhooks/actions/{orgId}/{webhookIdentifier}

Security Considerations

  • Signature Verification: All webhooks are verified using Svix HMAC signatures
  • Secret Rotation: Use the “Rotate Secret” button in the webhook settings to generate a new secret
  • Rate Limiting: Webhooks are rate-limited to 100 requests per minute per IP by default
  • Idempotency: Duplicate externalId values are detected and won’t create duplicate actions
  • Encryption: Webhook secrets are encrypted at rest

Troubleshooting

”Missing webhook signature headers”

Ensure all three Svix headers are present in your request:

  • svix-id
  • svix-timestamp
  • svix-signature

”Invalid signature”

  • Verify your webhook secret is correct
  • Ensure the secret is properly base64 encoded when generating the signature
  • Check that the timestamp is recent (within 5 minutes)

“Webhook not configured”

  • Verify the orgId in the URL matches your organization
  • Verify the webhookIdentifier matches what you configured

”Webhook is disabled”

Enable the webhook in ActionsWebhooks → click on your webhook → toggle “Enabled”

Action Not Appearing

  • Check if a rule is configured and enabled
  • Verify the rule is linked to the webhook as the default rule
  • Check API logs for any processing errors

Rate Limit Exceeded

Wait 60 seconds before sending more requests, or configure higher limits via environment variables:

  • WEBHOOK_RATE_LIMIT_WINDOW_MS (default: 60000)
  • WEBHOOK_RATE_LIMIT_MAX_REQUESTS (default: 100)
Last updated on