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.

How It Works

When an external event occurs in your system, AgentPress processes it through a structured pipeline:

1. External Event
   Your system detects an event (e.g., new review, form submission, CRM update)

2. Send Signed Webhook
   Your server (or the SDK) sends a signed HTTP POST to AgentPress
   with the event payload and Svix signature headers

3. AgentPress Creates Action
   The webhook is verified, deduplicated, and stored as a
   pending action linked to the matching action rule

4. Agent Processes Action
   The configured agent picks up the pending action, executes
   its instructions, and pauses if a tool requires approval

5. Post-Event Callbacks (optional)
   AgentPress sends signed lifecycle callbacks to your configured
   endpoint for pending approval, approved/running, and terminal events

6. Your Server Handles Lifecycle Events
   Your server verifies the callback signature, shows approval requests,
   and reacts to completed, failed, rejected, or expired actions

For the recommended approach using the @agentpress/sdk package, see Using the SDK.

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., "My Service Reviews")
Webhook IdentifierURL-safe identifier used in the endpoint (e.g., my_webhook)
Run As (User)Default user for actions created by this webhook. Optional — if left empty, the webhook payload must include userId (and authProvider for external users).
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;

  // Optional: User to run the action as.
  // If provided with authProvider, resolves via external auth.
  // If a valid UUID without authProvider, resolves via direct lookup.
  // If omitted, falls back to the webhook's configured default user.
  userId?: string;

  // Optional: External auth provider name (e.g., "google_business").
  // Required when userId is an external ID (not a UUID).
  authProvider?: string;

  // Optional: Override the default action rule
  actionRuleId?: string;

  // Optional: Additional per-request instructions for the agent
  instructions?: string;

  // Custom data specific to your integration
  data: {
    [key: string]: unknown;
  };
}

User Resolution

When a webhook is received, AgentPress resolves which user the action runs as by checking three sources in order:

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

Manual Test Script

Create a file called test-webhook.js:

const crypto = require("crypto");

const ORG_ID = "your-org-id-here";
const WEBHOOK_IDENTIFIER = "my_webhook";
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

Response Codes

CodeDescription
200Success - action created or already exists
401Missing or invalid signature headers
403Webhook is disabled
404Webhook configuration not found
422No user resolved — neither the payload nor the webhook config provided a valid user
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"
}

No User Resolved (422)

{
  "error": "No user resolved — provide userId (+ authProvider) in the webhook payload, or configure a default user on the webhook in the console."
}

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

Post-Event Callbacks

AgentPress can send signed HTTP callbacks as actions move through their lifecycle. A post-event callback can subscribe to any mix of these public event types:

Event TypeMeaning
action.pending_approvalA tool call is staged and needs approval.
action.approvedThe action was approved or is currently executing. Not terminal.
action.completedThe action finished successfully.
action.failedThe action failed during processing or execution.
action.rejectedThe staged action was rejected.
action.expiredThe staged action expired before approval.

Callbacks use the same Svix HMAC signing scheme as incoming webhooks, so your server can verify their authenticity using the same approach. Callback payloads include eventType, current status, occurredAt, optional stagedToolCall, and optional resultSummary.

Treat approved and executing as in-flight states. Only completed, failed, rejected, and expired are terminal.

For setup details, SDK-based signature verification, and payload examples, see Receiving Callbacks.

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"

"No user resolved" (422)

A user must be resolved from at least one source:

  • Include userId (+ authProvider for external IDs) in the webhook payload, or
  • Configure a default user on the webhook via ActionsWebhooks → click your webhook → Run As section

If neither is provided, the webhook returns 422 and no action is created.

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)

On this page