Triggers

Copy page

Create webhook endpoints that allow external services to invoke your agents

Triggers create webhook endpoints that allow external services (like GitHub, Slack, Stripe, or custom applications) to invoke your agents. When a webhook is received, the payload is validated, transformed, and used to start a new conversation with the agent.

Overview

Triggers are useful for:

  • Event-driven workflows - Respond to events from external services (GitHub issues, Stripe payments, etc.)
  • Third-party integrations - Connect services that can send webhooks to your agents
  • Automated tasks - Trigger agent actions based on external events
  • Custom applications - Allow your own services to invoke agents via HTTP

Creating Triggers

Basic Trigger

import { agent, trigger, subAgent } from "@inkeep/agents-sdk";

const slackTrigger = trigger({
  name: "Slack Messages",
  description: "Handle incoming Slack messages",
  messageTemplate: "New message from {{user.name}}: {{text}}",
});

export default agent({
  id: "support-agent",
  name: "Support Agent",
  defaultSubAgent: subAgent({
    id: "support",
    name: "Support Assistant",
    prompt: "You help answer support questions.",
  }),
  triggers: () => [slackTrigger],
});

Trigger with Input Validation

Use JSON Schema or Zod to validate incoming webhook payloads:

import { z } from "zod";

const githubWebhookTrigger = trigger({
  name: "GitHub Events",
  description: "Handle GitHub webhook events",
  inputSchema: z.object({
    action: z.string(),
    repository: z.object({
      full_name: z.string(),
    }),
    sender: z.object({
      login: z.string(),
    }),
  }),
  messageTemplate:
    "GitHub event: {{action}} on {{repository.full_name}} by {{sender.login}}",
  signingSecret: process.env.GITHUB_WEBHOOK_SECRET,
});

Trigger with Authentication

Triggers support flexible header-based authentication. You can require any headers with expected values:

Note
Note

Header values are securely hashed before storage. The original values are never stored in the database.

Configuration Options

OptionTypeRequiredDescription
namestringYesHuman-readable name for the trigger
descriptionstringNoDescription of what the trigger does
enabledbooleanNoWhether the trigger is active (default: true)
inputSchemaobject | ZodObjectNoJSON Schema or Zod schema for payload validation
messageTemplatestringYesTemplate for the message sent to the agent
authenticationobjectNoHeader-based authentication configuration
signingSecretstringNoHMAC-SHA256 secret for signature verification
outputTransformobjectNoPayload transformation configuration

Authentication Configuration

authentication: {
  headers: [
    { name: "Header-Name", value: "expected-value" },
    // Add more headers as needed
  ],
}

Each incoming request must include all configured headers with matching values. If any header is missing or has an incorrect value, the request is rejected with a 401 or 403 status.

Message Templates

Message templates use {{placeholder}} syntax to interpolate values from the webhook payload:

const trigger = trigger({
  name: "Order Webhook",
  messageTemplate: `
New order received:
- Order ID: {{order.id}}
- Customer: {{customer.name}} ({{customer.email}})
- Total: ${{order.total}}
- Items: {{order.items.length}} items

Please process this order and send a confirmation.
  `.trim(),
});

Nested properties are accessed with dot notation ({{customer.email}}). Array length can be accessed with .length.

Payload Transformation

Transform incoming payloads before they reach the message template using outputTransform. You can choose between two approaches:

ApproachBest ForComplexity
Object TransformationSimple field remappingLow
JMESPathComplex filtering, restructuring, array operationsHigh
Note
Note

These options are mutually exclusive. Use either jmespath OR objectTransformation, not both. If both are provided, jmespath takes priority.

Use objectTransformation when you just need to remap fields from the payload. Each key becomes a field in the transformed output, and each value is a JMESPath path to extract from the input:

const transformingTrigger = trigger({
  name: "Transformed Webhook",
  messageTemplate: "Event: {{eventType}} - {{summary}}",
  outputTransform: {
    // JMESPath expression for complex transformations
    jmespath: "{ eventType: type, summary: data.description }",
  },
});

// Or use simple object mapping
const mappingTrigger = trigger({
  name: "Mapped Webhook",
  messageTemplate: "User {{userName}} performed {{actionName}}",
  outputTransform: {
    objectTransformation: {
      userName: "payload.user.display_name",
      actionName: "payload.action.type",
    },
  },
});

// Input: { payload: { user: { display_name: "Alice" }, action: { type: "click" } } }
// Transformed: { userName: "Alice", actionName: "click" }
// Message: "User Alice performed click"

JMESPath (For Complex Transformations)

Use jmespath when you need more powerful transformations like filtering arrays, conditional logic, or complex restructuring. JMESPath is a query language for JSON:

const transformingTrigger = trigger({
  name: "Transformed Webhook",
  messageTemplate: "Event: {{eventType}} - {{summary}}",
  outputTransform: {
    jmespath: "{ eventType: type, summary: data.description }",
  },
  authentication: { type: "none" },
});

// Input: { type: "alert", data: { description: "Server down" } }
// Transformed: { eventType: "alert", summary: "Server down" }
// Message: "Event: alert - Server down"

Advanced JMESPath example - filtering and projecting arrays:

const advancedTrigger = trigger({
  name: "Active Users Alert",
  messageTemplate: "Active users: {{activeUserNames}}",
  outputTransform: {
    jmespath: "{ activeUserNames: join(', ', users[?active==`true`].name) }",
  },
  authentication: { type: "none" },
});

// Input: { users: [{ name: "Alice", active: true }, { name: "Bob", active: false }] }
// Transformed: { activeUserNames: "Alice" }
// Message: "Active users: Alice"
Tip
Tip

Object Transformation is converted to JMESPath under the hood. For example, { userName: "user.name" } becomes the JMESPath expression { userName: user.name }.

Signature Verification

For services that sign webhooks (like GitHub, Stripe, Slack), use signingSecret:

const githubTrigger = trigger({
  name: "GitHub Webhook",
  messageTemplate: "GitHub: {{action}} on {{repository.full_name}}",
  signingSecret: process.env.GITHUB_WEBHOOK_SECRET,
});

The framework automatically verifies HMAC-SHA256 signatures in the X-Signature-256 header.

Tip
Tip

For services like GitHub that sign webhooks, you often don't need header authentication—the signature verification provides security. Use signingSecret alone for these cases.

Webhook URL

After pushing your agent configuration, each trigger gets a unique webhook URL:

POST /tenants/{tenantId}/projects/{projectId}/agents/{agentId}/triggers/{triggerId}

You can find the full webhook URL in the Inkeep dashboard or via the API response when creating/listing triggers.

Invocation Tracking

Every webhook invocation is tracked with:

  • Invocation ID - Unique identifier for the invocation
  • Status - pending, success, or failed
  • Request Payload - Original webhook payload
  • Transformed Payload - Payload after transformation
  • Conversation ID - ID of the conversation created
  • Error Message - Error details if the invocation failed

Query invocation history via the API:

# List invocations for a trigger
curl "https://api.inkeep.com/v1/tenants/{tenantId}/projects/{projectId}/agents/{agentId}/triggers/{triggerId}/invocations"

# Filter by status or date range
curl "https://api.inkeep.com/v1/.../invocations?status=failed&from=2024-01-01T00:00:00Z"

Complete Example

Here's a complete example with a GitHub webhook trigger that handles issue events:

import { z } from "zod";
import { agent, trigger, subAgent } from "@inkeep/agents-sdk";

// Define the expected GitHub webhook payload
const githubIssueSchema = z.object({
  action: z.enum(["opened", "closed", "reopened", "edited"]),
  issue: z.object({
    number: z.number(),
    title: z.string(),
    body: z.string().nullable(),
    user: z.object({
      login: z.string(),
    }),
    labels: z.array(
      z.object({
        name: z.string(),
      })
    ),
  }),
  repository: z.object({
    full_name: z.string(),
  }),
});

const githubIssueTrigger = trigger({
  name: "GitHub Issues",
  description: "Responds to GitHub issue events",
  enabled: true,
  inputSchema: githubIssueSchema,
  messageTemplate: `
GitHub Issue {{action}}:
- Repository: {{repository.full_name}}
- Issue #{{issue.number}}: {{issue.title}}
- Author: {{issue.user.login}}
- Body: {{issue.body}}

Please analyze this issue and provide a helpful response.
  `.trim(),
  signingSecret: process.env.GITHUB_WEBHOOK_SECRET,
});

const issueResponder = subAgent({
  id: "issue-responder",
  name: "Issue Responder",
  prompt: `You are a helpful assistant that responds to GitHub issues.
When an issue is opened, analyze the content and provide:
1. A summary of the issue
2. Suggested labels if appropriate
3. Initial guidance or questions for the issue author`,
});

export default agent({
  id: "github-bot",
  name: "GitHub Bot",
  defaultSubAgent: issueResponder,
  triggers: () => [githubIssueTrigger],
});

Best Practices

  1. Always validate input - Use inputSchema to ensure payloads match expected format
  2. Use signing secrets - Verify webhook authenticity when the source supports it
  3. Use header auth for custom webhooks - When the source doesn't sign webhooks, use header-based authentication
  4. Write clear templates - Make message templates readable and include relevant context
  5. Handle errors gracefully - Check invocation status and logs for failed webhooks
  6. Use descriptive names - Name triggers clearly to identify their purpose in dashboards
  7. Store secrets in environment variables - Never hardcode authentication values