Using the SDK
@agentpress/sdk is the official TypeScript SDK for interacting with the AgentPress webhook system. It handles HMAC-SHA256 signing, payload construction, signature verification, 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 | -- | Svix signing secret (must start with whsec_). Required for send and verify 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}.
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
Response
The send() method returns a WebhookResponse:
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the action was created successfully. |
actionId | string? | UUID of the created action. |
alreadyExists | boolean? | true if a duplicate externalId was detected (idempotency). |
skipped | boolean? | true if no matching action rule is configured for this webhook. |
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.