Using the SDK
@agentpress/sdk is the official TypeScript SDK for interacting with the AgentPress webhook system. It handles action webhook sending, Svix/HMAC/shared-token signing, outbound callback verification, action approval/rejection, and Partner MCP verification. Dual ESM and CommonJS output, requires Node 22 or later.
Installation
npm install @agentpress/sdk
# or
bun add @agentpress/sdkClient Setup
Create an AgentPress client instance with your webhook secret and organization slug:
import { AgentPress } from "@agentpress/sdk";
const client = new AgentPress({
webhookSecret: "whsec_your_secret_here",
org: "your-org-slug",
baseUrl: "https://api.agent.press",
});Options
| Option | Type | Default | Description |
|---|---|---|---|
webhookSecret | string | -- | Webhook secret. Required for default Svix sends, HMAC/shared-token sends without an auth override, callback verification, and action approval operations. |
org | string | "default-org" | Organization slug used in the webhook URL path. |
baseUrl | string | "https://api.agent.press" | API base URL. Trailing slashes are stripped automatically. |
timeout | number | 30000 | Request timeout in milliseconds. Must be a positive finite number. |
apiKey | string | -- | API key for authenticated requests (reserved for future use). |
onRequest | (url: string, init: RequestInit) => void | -- | Hook called before every outbound HTTP request. Useful for logging or tracing. |
onResponse | (url: string, response: Response) => void | -- | Hook called after every HTTP response is received. |
The client validates all options at construction time. A webhookSecret that does not start with whsec_ or a non-positive timeout will throw a ConfigurationError immediately.
Sending Events
Basic Events
Use client.webhooks.send() to sign and send an arbitrary webhook payload to AgentPress:
const response = await client.webhooks.send({
action: "my_webhook_action",
payload: {
eventType: "order.completed",
externalId: "order-456",
data: { total: 99.99 },
},
});This sends a POST request to {baseUrl}/webhooks/actions/{org}/{action}.
action is the action webhook identifier from the AgentPress webhook detail
page.
By default, new action webhooks use verificationScheme: "svix", and the SDK
handles the full Svix signing flow automatically:
- Generates a unique message ID (
msg_<uuid>) for thesvix-idheader - Creates a Unix timestamp for the
svix-timestampheader - Signs the payload with HMAC-SHA256 using your webhook secret
- Sends the JSON body with all three Svix headers attached
If the action webhook is configured for another verification scheme, pass
auth:
await client.webhooks.send({
action: "billing_events",
auth: {
scheme: "hmac_sha256",
secret: process.env.AGENTPRESS_WEBHOOK_SECRET!,
},
payload: {
eventType: "invoice.created",
externalId: "invoice-123",
data: { total: 99.99 },
},
});| Scheme | SDK auth | Notes |
|---|---|---|
svix | { scheme: "svix", secret? } | Default. Uses webhookSecret when secret is omitted. |
hmac_sha256 | { scheme: "hmac_sha256", secret? } | Adds x-webhook-timestamp and x-webhook-signature. |
shared_token | { scheme: "shared_token", token? } | Adds x-webhook-token. Uses webhookSecret when token is omitted. |
none | { scheme: "none" } | Sends no verification headers. Use only for capability-URL webhooks intentionally configured with no header verification. |
Response
The send() method returns a WebhookResponse:
| Field | Type | Description |
|---|---|---|
success | boolean | Whether AgentPress accepted the request. |
actionId | string? | UUID of the created or existing action. |
alreadyExists | boolean? | true if a duplicate externalId was detected and the existing action was returned. |
buffered | boolean? | true when AgentPress accepted the delivery with HTTP 202 but could not create the action yet. |
eventId | string? | UUID of the persisted webhook event for buffered or skipped deliveries. |
reason | string? | Machine-readable reason such as no_action_rule_configured, action_rule_disabled, or user_unresolved. |
skipped | boolean? | Compatibility field for intentionally skipped delivery rows. |
Configuration and resolution gaps now return 202 with buffered: true
instead of dropping the event. Check response.buffered before assuming
response.actionId exists.
Compatibility Notes
The current actions webhook update is source-compatible for existing SDK
callers that already use client.webhooks.send() with the default Svix action
webhook. No exported SDK method was removed.
Sender changes are required only when the receiving webhook configuration changes:
- For
verificationScheme: "hmac_sha256", passauth: { scheme: "hmac_sha256", secret }. - For
verificationScheme: "shared_token", passauth: { scheme: "shared_token", token }. - For
verificationScheme: "none", passauth: { scheme: "none" }and use the latest copied capability URL identifier. - Existing listener aliases remain available for older integrations, but new
code should use
client.webhooks.send()and/webhooks/actions/{org}/{identifier}. - Treat
202plusbuffered: trueas accepted delivery without an action yet; branch onresponse.bufferedbefore readingresponse.actionId.
Action Events
Use client.webhooks.send() for any external event that should create or update an AgentPress action. Include a stable externalId when retries are possible so the API can de-dupe repeated deliveries:
const response = await client.webhooks.send({
action: "review_response",
payload: {
eventType: "review.created",
externalId: "review-123",
data: {
rating: 4,
text: "Good experience overall.",
reviewer: "Jane Smith",
locationName: "Downtown Office",
},
},
});Verifying Inbound Webhooks
When AgentPress sends action lifecycle callbacks to your server, use the verification methods to validate the Svix signature.
verify
Returns a boolean indicating whether the signature is valid:
const isValid = client.webhooks.verify({
payload: rawRequestBody,
headers: {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
},
});
if (!isValid) {
return res.status(401).json({ error: "Invalid signature" });
}verifyOrThrow
Throws a WebhookSignatureError on invalid or expired signatures. Useful in middleware patterns:
try {
client.webhooks.verifyOrThrow({
payload: rawRequestBody,
headers: {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
},
});
} catch (error) {
return res.status(401).json({ error: "Invalid signature" });
}constructEvent
Combines signature verification with JSON parsing. This is the recommended method for handling inbound webhooks:
import type { ActionCallbackPayload } from "@agentpress/sdk";
const event: ActionCallbackPayload = client.webhooks.constructEvent({
payload: rawRequestBody,
headers: {
"svix-id": req.headers["svix-id"],
"svix-timestamp": req.headers["svix-timestamp"],
"svix-signature": req.headers["svix-signature"],
},
});
console.log(`Action ${event.actionId} emitted ${event.eventType}`);
console.log(`Current status: ${event.status}`);
console.log(`Result summary: ${event.resultSummary ?? "not available yet"}`);Throws WebhookSignatureError if the signature is invalid, or AgentPressError if the payload is not valid JSON.
Signatures expire after 5 minutes. Requests with a svix-timestamp older than that are rejected.
Callback payloads include eventType, current status, occurredAt for the specific lifecycle callback, completedAt for backwards compatibility, resultSummary when the action has a generated outcome summary, and stagedToolCall.summary for approval callbacks. The public callback event union is action.pending_approval, action.approved, action.completed, action.failed, action.rejected, and action.expired; actions with status executing are represented externally as action.approved.
Treat approved and executing as in-flight states. They mean the action has been approved or is running, not that the work succeeded. Only completed, failed, rejected, and expired are terminal.
Handling Approval Lifecycle Events
Switch on event.eventType for behavior, and use event.status as display state:
switch (event.eventType) {
case "action.pending_approval":
console.log("Approval needed for", event.stagedToolCall?.toolName);
break;
case "action.approved":
console.log(`Action ${event.actionId} is ${event.status}`);
break;
case "action.completed":
console.log(event.resultSummary ?? event.agentResponse.text);
break;
case "action.failed":
console.error(event.errorMessage ?? "Action failed");
break;
case "action.rejected":
case "action.expired":
console.log(`Action ended with ${event.eventType}`);
break;
}When approving or rejecting from your server, pass the callback's webhookAction back to the SDK:
await client.actions.approve(event.actionId, {
action: event.webhookAction!,
remember: "once",
});Use remember: "webhook" to auto-approve future triggers from the same user, webhook, and tool combination:
await client.actions.approve(event.actionId, {
action: event.webhookAction!,
remember: "webhook",
});Partner Approval Settings
Partner apps can render the same per-webhook approval settings from SDK metadata:
const metadata = await client.userApprovals.getWebhookMetadata({
webhookIdentifier: "partner_actions_webhook",
});
for (const tool of metadata.availableTools) {
console.log(tool.toolName, tool.label, tool.description);
}Use metadata.availableTools as the source of truth for tool names, labels, and ordering. The order matches the AgentPress approval popup. Each tool includes toolName, label, description, destructiveHint, and sensitiveHint when available. metadata.actionWebhookId is returned for compatibility, but current AgentPress core resolves it from webhookIdentifier for SDK approval writes.
Read a specific external user's current rules with userId plus authProvider:
const { approvals } = await client.userApprovals.list({
webhookIdentifier: metadata.webhookIdentifier,
userId: "external_user_123",
authProvider: "partner_auth",
});If userId is omitted, list returns all approval rules for the signed webhook. For partner user-settings screens, pass userId and authProvider.
Create or update one Allow rule with a tool selected from metadata:
const selectedTool = getSelectedToolFromYourUi(metadata.availableTools);
await client.userApprovals.create({
webhookIdentifier: metadata.webhookIdentifier,
userId: "external_user_123",
authProvider: "partner_auth",
toolName: selectedTool.toolName,
mode: "always_allow",
});Sync Allow/Ask defaults from a partner settings UI with sync:
const allowByToolName: Record<string, boolean> = getSettingsFromYourUi();
const result = await client.userApprovals.sync({
webhookIdentifier: metadata.webhookIdentifier,
userId: "external_user_123",
authProvider: "partner_auth",
rules: metadata.availableTools.map((tool) => ({
toolName: tool.toolName,
mode: allowByToolName[tool.toolName] ? "always_allow" : "ask",
})),
});
console.log(result.summary);
console.log(result.preservedDenyTools);sync is idempotent. always_allow creates or updates an allow rule. ask removes an existing allow rule for that listed tool. Existing always_deny rows are preserved and returned as preservedDenyTools; rules for tools omitted from the request are not deleted.
Approval rows require an internal AgentPress user who belongs to the webhook's org. If the external user has not authenticated through that provider yet, or the resolved user is not an org member, AgentPress returns HTTP 422 with code: "USER_NOT_PROVISIONED". Send the user through the partner external-auth flow once, ensure they belong to the org, then retry the approval sync.
If you edit a staged tool call, send the full tool call you want AgentPress to run:
await client.actions.approve(event.actionId, {
action: event.webhookAction!,
editedToolCall: {
toolName: event.stagedToolCall!.toolName,
arguments: {
...event.stagedToolCall!.arguments,
subject: "Updated subject",
},
},
});stagedToolCall.summary can be a plain string, a structured { title, detail } object, or absent on older rows. resultSummary is also optional, so keep a fallback such as agentResponse.text or errorMessage.
For full details on callback payload structure and setup, see Receiving Callbacks.
Error Handling
The SDK uses a typed error hierarchy. All errors extend AgentPressError, so you can catch broadly or handle specific error types:
AgentPressError (base)
├── ConfigurationError — invalid options or missing webhookSecret
├── HttpError — non-2xx response (has statusCode, responseBody, url)
├── TimeoutError — request exceeded configured timeout
└── WebhookSignatureError — invalid or expired signatureExample
import {
AgentPress,
AgentPressError,
ConfigurationError,
HttpError,
TimeoutError,
} from "@agentpress/sdk";
try {
await client.webhooks.send({ action: "test", payload: {} });
} catch (error) {
if (error instanceof HttpError) {
console.error(`HTTP ${error.statusCode}: ${error.responseBody}`);
console.error(`URL: ${error.url}`);
} else if (error instanceof TimeoutError) {
console.error("Request timed out");
} else if (error instanceof ConfigurationError) {
console.error("Invalid configuration:", error.message);
} else if (error instanceof AgentPressError) {
console.error("SDK error:", error.message);
}
}Exported Types
All types are exported from the package root for use in your application code:
import type {
ActionEventType,
ActionCallbackPayload,
ActionStatus,
AgentPressOptions,
AgentResponse,
ApprovalMode,
ApprovalRuleSyncMode,
CreateUserApprovalParams,
DeleteUserApprovalParams,
GetUserApprovalWebhookMetadataParams,
ListUserApprovalsParams,
ListUserApprovalsResponse,
StagedToolCall,
StagedToolCallSummary,
SyncUserApprovalRule,
SyncUserApprovalsParams,
SyncUserApprovalsResponse,
ToolCallResult,
UpdateUserApprovalParams,
UserToolApproval,
WebhookApprovalMetadata,
WebhookApprovalToolMetadata,
WebhookResponse,
WebhookSendParams,
WebhookVerifyParams,
} from "@agentpress/sdk";The ActionCallbackPayload, AgentResponse, and ToolCallResult types are covered in detail in Receiving Callbacks. Use WebhookApprovalMetadata.availableTools and UserToolApproval when building partner approval settings.