@agentpress/sdk 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ TypeScript SDK for the AgentPress webhook API.
5
5
  - **Package:** `@agentpress/sdk` (npm, public)
6
6
  - **Output:** Dual ESM (`.mjs`) + CJS (`.cjs`)
7
7
  - **Node:** >= 22.0.0
8
- - **Dependencies:** `jose` for Partner MCP JWT verification; webhook signing uses `node:crypto`
8
+ - **Dependencies:** `jose` for Partner MCP JWT verification; action webhook signing uses `node:crypto`
9
9
 
10
10
  ## Installation
11
11
 
@@ -35,7 +35,7 @@ await client.webhooks.send({
35
35
 
36
36
  ```typescript
37
37
  const client = new AgentPress({
38
- webhookSecret: "whsec_...", // Required for send/verify (must start with "whsec_")
38
+ webhookSecret: "whsec_...", // Required for default Svix sends, verify, and action management
39
39
  baseUrl: "https://api.agent.press", // Default
40
40
  timeout: 30_000, // Default, in milliseconds
41
41
  org: "default-org", // Default org slug
@@ -45,7 +45,9 @@ const client = new AgentPress({
45
45
  });
46
46
  ```
47
47
 
48
- All options are optional. `webhookSecret` is required when calling `send`, `verify`, `verifyOrThrow`, `constructEvent`, or action management methods.
48
+ All options are optional. `webhookSecret` is required when calling `send` with
49
+ the default Svix signing mode, HMAC/shared-token sends without an auth override,
50
+ `verify`, `verifyOrThrow`, `constructEvent`, or action management methods.
49
51
 
50
52
  **Validation rules:**
51
53
  - `webhookSecret` must start with `"whsec_"` or a `ConfigurationError` is thrown
@@ -58,7 +60,10 @@ The client exposes `client.webhooks`, `client.actions`, `client.userApprovals`,
58
60
 
59
61
  ### client.webhooks.send(params)
60
62
 
61
- Signs and sends an arbitrary webhook payload via HMAC-SHA256.
63
+ Sends an arbitrary action webhook payload. By default the SDK uses Svix-style
64
+ signing with the client's `webhookSecret`, which matches the default
65
+ verification scheme for new AgentPress action webhooks. Pass `auth` to match
66
+ another verification scheme configured for the action webhook.
62
67
 
63
68
  ```typescript
64
69
  const response = await client.webhooks.send({
@@ -73,26 +78,55 @@ const response = await client.webhooks.send({
73
78
 
74
79
  **Endpoint:** `POST {baseUrl}/webhooks/actions/{org}/{action}`
75
80
 
81
+ `action` is the action webhook identifier shown on the AgentPress webhook detail
82
+ page. The older `/webhooks/ingest/{org}/{identifier}` listener endpoint is kept
83
+ only for compatibility APIs; new integrations should use `send()`.
84
+
76
85
  **Params (`WebhookSendParams`):**
77
86
 
78
87
  | Field | Type | Description |
79
88
  |-------|------|-------------|
80
89
  | `action` | `string` | Webhook action identifier (used in URL path) |
81
90
  | `payload` | `Record<string, unknown>` | Arbitrary JSON payload to sign and send |
91
+ | `auth` | `WebhookSendAuth` | Optional verification override. Defaults to Svix with the client's `webhookSecret`. |
82
92
 
83
93
  **Returns (`WebhookResponse`):**
84
94
 
85
95
  ```typescript
86
96
  {
87
97
  success: boolean;
88
- actionId?: string; // ID of created action
98
+ actionId?: string; // ID of created or existing action
89
99
  alreadyExists?: boolean; // Duplicate externalId
90
- skipped?: boolean;
100
+ skipped?: boolean; // Compatibility field for skipped deliveries
101
+ buffered?: boolean; // HTTP 202: accepted but waiting on configuration
102
+ eventId?: string; // Buffered/skipped webhook_events row
103
+ reason?: string; // Machine-readable buffered/skipped reason
91
104
  data?: Record<string, unknown>;
92
105
  }
93
106
  ```
94
107
 
95
- **Throws:** `ConfigurationError` (missing secret), `HttpError` (non-2xx), `TimeoutError`
108
+ Configuration/resolution gaps such as a missing action rule, disabled rule, or
109
+ unresolved user return HTTP `202` with `buffered: true` instead of throwing.
110
+ The delivery is persisted and appears in the Actions ledger for debugging and
111
+ retry.
112
+
113
+ **Compatibility notes:**
114
+ - Existing `client.webhooks.send()` callers continue to work for action
115
+ webhooks using the default Svix verification scheme. No SDK method was
116
+ removed.
117
+ - If the webhook is configured for `hmac_sha256`, `shared_token`, or `none`,
118
+ pass the matching `auth` option. Manual sender scripts must send the matching
119
+ headers.
120
+ - Existing listener aliases and `/webhooks/ingest/{org}/{identifier}` remain
121
+ compatibility paths, but new integrations should use `send()` and
122
+ `/webhooks/actions/{org}/{action}`.
123
+ - `202` with `buffered: true` is accepted delivery without a created action yet;
124
+ check `response.buffered` before using `response.actionId`.
125
+ - For `verificationScheme: "none"`, the URL is the credential and can rotate
126
+ when the webhook is created, switched to `none`, or rotated. Copy the latest
127
+ URL after those operations.
128
+
129
+ **Throws:** `ConfigurationError` (missing required secret/token), `HttpError` (non-2xx), `TimeoutError`
96
130
 
97
131
  **Signing details:** Each request gets three headers automatically:
98
132
  - `svix-id` -- unique message ID (`msg_<uuid>`)
@@ -101,6 +135,34 @@ const response = await client.webhooks.send({
101
135
 
102
136
  The signature input is `${msgId}.${timestamp}.${body}`.
103
137
 
138
+ ```typescript
139
+ await client.webhooks.send({
140
+ action: "billing_events",
141
+ auth: {
142
+ scheme: "hmac_sha256",
143
+ secret: process.env.AGENTPRESS_WEBHOOK_SECRET!,
144
+ },
145
+ payload: {
146
+ eventType: "invoice.created",
147
+ data: { invoiceId: "inv_123" },
148
+ },
149
+ });
150
+ ```
151
+
152
+ **Auth schemes:**
153
+
154
+ | Scheme | Params | Notes |
155
+ |--------|--------|-------|
156
+ | `svix` | `{ secret?, msgId?, timestamp? }` | Adds `svix-id`, `svix-timestamp`, and `svix-signature`. Uses `webhookSecret` when `secret` is omitted. |
157
+ | `hmac_sha256` | `{ secret?, timestamp? }` | Adds `x-webhook-timestamp` and `x-webhook-signature` using `signHmacWebhookRequest`. Uses `webhookSecret` when `secret` is omitted. |
158
+ | `shared_token` | `{ token? }` | Adds `x-webhook-token`. Uses `webhookSecret` when `token` is omitted. |
159
+ | `none` | none | Sends no verification headers. Only use for capability-URL action webhooks intentionally configured with no header verification. |
160
+
161
+ For `verificationScheme: "none"`, the action webhook URL itself is the
162
+ credential. Creating, switching, or rotating such a webhook may mint a new
163
+ unguessable `webhookIdentifier`; copy the latest URL from AgentPress before
164
+ updating manual sender scripts.
165
+
104
166
  ---
105
167
 
106
168
  ### client.webhooks.verify(params)
package/dist/index.cjs CHANGED
@@ -607,7 +607,57 @@ function validatePayload(raw) {
607
607
  return event;
608
608
  }
609
609
  //#endregion
610
+ //#region src/webhooks/actionWebhookSigning.ts
611
+ /**
612
+ * Sign an outbound request for an AgentPress action webhook that uses the
613
+ * `hmac_sha256` verification scheme
614
+ * (`POST /webhooks/actions/:org/:identifier`).
615
+ *
616
+ * Produces the two headers AgentPress verifies:
617
+ *
618
+ * - `x-webhook-timestamp` — unix seconds; AgentPress rejects timestamps more
619
+ * than 5 minutes from its own clock.
620
+ * - `x-webhook-signature` — `v1=<hex HMAC-SHA256 of "${timestamp}.${rawBody}">`.
621
+ *
622
+ * Send the exact `rawBody` string you signed — any re-serialization after
623
+ * signing (re-ordered keys, whitespace changes) invalidates the signature.
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * const rawBody = JSON.stringify({ eventType: "review.created", data: {...} });
628
+ * const { headers } = signHmacWebhookRequest({ secret, rawBody });
629
+ * await fetch(actionWebhookUrl, {
630
+ * method: "POST",
631
+ * body: rawBody,
632
+ * headers: { "content-type": "application/json", ...headers },
633
+ * });
634
+ * ```
635
+ */
636
+ function signHmacWebhookRequest(params) {
637
+ const timestamp = Math.floor(params.timestamp ?? Date.now() / 1e3).toString();
638
+ return { headers: {
639
+ "x-webhook-timestamp": timestamp,
640
+ "x-webhook-signature": `v1=${(0, node_crypto.createHmac)("sha256", params.secret).update(`${timestamp}.${params.rawBody}`).digest("hex")}`
641
+ } };
642
+ }
643
+ /**
644
+ * Build the auth header for an AgentPress action webhook that uses the
645
+ * `shared_token` verification scheme. AgentPress also accepts
646
+ * `Authorization: Bearer <token>`; this helper uses the dedicated
647
+ * `x-webhook-token` header so it never collides with other auth middleware.
648
+ */
649
+ function sharedTokenHeaders(token) {
650
+ return { "x-webhook-token": token };
651
+ }
652
+ //#endregion
610
653
  //#region src/webhooks/client.ts
654
+ function pathSegment(value) {
655
+ return encodeURIComponent(value);
656
+ }
657
+ function requireSecret(scheme, secret) {
658
+ if (!secret) throw new ConfigurationError(`webhookSecret or auth.${scheme === "shared_token" ? "token" : "secret"} is required for ${scheme} action webhook sends`);
659
+ return secret;
660
+ }
611
661
  var WebhooksClient = class {
612
662
  options;
613
663
  http;
@@ -617,7 +667,8 @@ var WebhooksClient = class {
617
667
  }
618
668
  /**
619
669
  * Send an arbitrary webhook payload to AgentPress.
620
- * Signs the payload with HMAC-SHA256 (Svix-compatible).
670
+ * Signs the payload with the verification scheme configured on the action
671
+ * webhook. Defaults to Svix-compatible signing.
621
672
  *
622
673
  * On the happy path the response is synchronous: `{ success: true,
623
674
  * actionId, data }`, or `{ success, actionId, alreadyExists: true, data }`
@@ -630,28 +681,81 @@ var WebhooksClient = class {
630
681
  * auto-processes once an operator fixes the configuration, so check
631
682
  * {@link WebhookResponse.buffered} before relying on `actionId`.
632
683
  *
633
- * @throws ConfigurationError if webhookSecret is not configured
684
+ * @throws ConfigurationError if the selected verification scheme needs a
685
+ * secret/token and none is configured
634
686
  * @throws HttpError on non-2xx response
635
687
  * @throws TimeoutError if request exceeds timeout
636
688
  */
637
689
  async send(params) {
638
- if (!this.options.webhookSecret) throw new ConfigurationError("webhookSecret is required for webhook operations");
639
- const path = `/webhooks/actions/${this.options.org}/${params.action}`;
690
+ const path = `/webhooks/actions/${pathSegment(this.options.org)}/${pathSegment(params.action)}`;
640
691
  const body = JSON.stringify(params.payload);
641
- const msgId = randomMessageId();
642
- const timestamp = Math.floor(Date.now() / 1e3);
643
- const signature = sign(this.options.webhookSecret, msgId, timestamp, body);
692
+ const auth = params.auth ?? { scheme: "svix" };
693
+ const headers = {};
694
+ if (auth.scheme === "svix") {
695
+ const secret = requireSecret("svix", auth.secret ?? this.options.webhookSecret);
696
+ const msgId = auth.msgId ?? randomMessageId();
697
+ const timestamp = Math.floor(auth.timestamp ?? Date.now() / 1e3);
698
+ Object.assign(headers, {
699
+ "svix-id": msgId,
700
+ "svix-timestamp": String(timestamp),
701
+ "svix-signature": sign(secret, msgId, timestamp, body)
702
+ });
703
+ } else if (auth.scheme === "hmac_sha256") {
704
+ const secret = requireSecret("hmac_sha256", auth.secret ?? this.options.webhookSecret);
705
+ Object.assign(headers, signHmacWebhookRequest({
706
+ secret,
707
+ rawBody: body,
708
+ timestamp: auth.timestamp
709
+ }).headers);
710
+ } else if (auth.scheme === "shared_token") {
711
+ const token = requireSecret("shared_token", auth.token ?? this.options.webhookSecret);
712
+ Object.assign(headers, sharedTokenHeaders(token));
713
+ }
644
714
  return this.http.request(path, {
645
715
  method: "POST",
646
716
  body,
647
- headers: {
717
+ headers
718
+ });
719
+ }
720
+ /**
721
+ * Send a payload to the legacy Actions listener ingestion endpoint
722
+ * (`POST /webhooks/ingest/:org/:identifier`).
723
+ *
724
+ * New integrations should prefer {@link send}; this method is kept for
725
+ * existing listener integrations and SDK patch compatibility.
726
+ */
727
+ async sendToActionsListener(params) {
728
+ const path = `/webhooks/ingest/${pathSegment(this.options.org)}/${pathSegment(params.identifier)}`;
729
+ const body = JSON.stringify(params.payload);
730
+ const headers = {};
731
+ if (params.auth.scheme === "svix") {
732
+ const msgId = params.auth.msgId ?? randomMessageId();
733
+ const timestamp = Math.floor(params.auth.timestamp ?? Date.now() / 1e3);
734
+ Object.assign(headers, {
648
735
  "svix-id": msgId,
649
736
  "svix-timestamp": String(timestamp),
650
- "svix-signature": signature
651
- }
737
+ "svix-signature": sign(params.auth.secret, msgId, timestamp, body)
738
+ });
739
+ } else if (params.auth.scheme === "hmac_sha256") Object.assign(headers, signHmacWebhookRequest({
740
+ secret: params.auth.secret,
741
+ rawBody: body,
742
+ timestamp: params.auth.timestamp
743
+ }).headers);
744
+ else if (params.auth.scheme === "shared_token") Object.assign(headers, sharedTokenHeaders(params.auth.token));
745
+ return this.http.request(path, {
746
+ method: "POST",
747
+ body,
748
+ headers
652
749
  });
653
750
  }
654
751
  /**
752
+ * @deprecated Use {@link sendToActionsListener}. Kept as a compatibility
753
+ * alias for integrations created before the Actions listener naming update.
754
+ */
755
+ async sendToListener(params) {
756
+ return this.sendToActionsListener(params);
757
+ }
758
+ /**
655
759
  * Verify an inbound Svix webhook signature.
656
760
  *
657
761
  * @returns true if valid, false if invalid or expired
@@ -793,49 +897,6 @@ const ACTION_EVENT_TYPES = [
793
897
  "action.expired"
794
898
  ];
795
899
  //#endregion
796
- //#region src/webhooks/ingestSigning.ts
797
- /**
798
- * Sign an outbound request for an AgentPress inbound webhook listener that
799
- * uses the `hmac_sha256` verification scheme
800
- * (`POST /webhooks/ingest/:org/:identifier`).
801
- *
802
- * Produces the two headers AgentPress verifies:
803
- *
804
- * - `x-webhook-timestamp` — unix seconds; AgentPress rejects timestamps more
805
- * than 5 minutes from its own clock.
806
- * - `x-webhook-signature` — `v1=<hex HMAC-SHA256 of "${timestamp}.${rawBody}">`.
807
- *
808
- * Send the exact `rawBody` string you signed — any re-serialization after
809
- * signing (re-ordered keys, whitespace changes) invalidates the signature.
810
- *
811
- * @example
812
- * ```ts
813
- * const rawBody = JSON.stringify({ eventType: "review.created", data: {...} });
814
- * const { headers } = signHmacWebhookRequest({ secret, rawBody });
815
- * await fetch(ingestUrl, {
816
- * method: "POST",
817
- * body: rawBody,
818
- * headers: { "content-type": "application/json", ...headers },
819
- * });
820
- * ```
821
- */
822
- function signHmacWebhookRequest(params) {
823
- const timestamp = Math.floor(params.timestamp ?? Date.now() / 1e3).toString();
824
- return { headers: {
825
- "x-webhook-timestamp": timestamp,
826
- "x-webhook-signature": `v1=${(0, node_crypto.createHmac)("sha256", params.secret).update(`${timestamp}.${params.rawBody}`).digest("hex")}`
827
- } };
828
- }
829
- /**
830
- * Build the auth header for an AgentPress inbound webhook listener that uses
831
- * the `shared_token` verification scheme. AgentPress also accepts
832
- * `Authorization: Bearer <token>`; this helper uses the dedicated
833
- * `x-webhook-token` header so it never collides with other auth middleware.
834
- */
835
- function sharedTokenHeaders(token) {
836
- return { "x-webhook-token": token };
837
- }
838
- //#endregion
839
900
  exports.ACTION_EVENT_TYPES = ACTION_EVENT_TYPES;
840
901
  exports.ActionsClient = ActionsClient;
841
902
  exports.AgentPress = AgentPress;