@10x-media/webhooks 0.1.0-beta.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/exports/client.d.ts +14 -0
- package/dist/exports/client.js +83 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/i18n.d.ts +37 -0
- package/dist/exports/i18n.js +3 -0
- package/dist/exports/types.d.ts +2 -0
- package/dist/exports/types.js +1 -0
- package/dist/index-CpsTtHO1.d.ts +62 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +849 -0
- package/dist/index.js.map +1 -0
- package/dist/keys-BdNiD3rC.js +31 -0
- package/dist/keys-BdNiD3rC.js.map +1 -0
- package/dist/translations-CnkwrCJ3.js +51 -0
- package/dist/translations-CnkwrCJ3.js.map +1 -0
- package/package.json +105 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @10x-media/webhooks
|
|
2
|
+
|
|
3
|
+
## 0.1.0-beta.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial beta of `@10x-media/webhooks`: outbound webhook subscriptions for Payload v3.
|
|
8
|
+
|
|
9
|
+
- **Subscriptions**: an admin-managed collection for registering endpoint URLs, selecting events, and storing per-subscription secrets. A 48-character hex secret is auto-generated on create.
|
|
10
|
+
- **Deliveries log**: an append-only collection with derived status, HTTP response code, and a redeliver button that replays the original payload to the original URL.
|
|
11
|
+
- **Event hooks**: opt any collection in with `collections: { posts: true }`. Emits `<slug>.created`, `<slug>.updated`, and `<slug>.deleted` events. Per-collection `operations`, `transform`, and `includePreviousData` options.
|
|
12
|
+
- **HMAC signing**: `X-Webhook-Signature: v1=<hex>` on every request when a subscription has a secret. Signed over `${timestamp}.${rawBody}`.
|
|
13
|
+
- **Delivery modes**: `inline` (awaited in the hook), `queue` (Payload jobs task with configurable retries and queue), and `auto` (queue when a runner is detected, inline otherwise).
|
|
14
|
+
- **Code subscriptions**: hard-coded subscriptions in plugin options, merged with admin-managed ones at delivery time.
|
|
15
|
+
- **Composable**: auto-detects `@10x-media/jobs` (uses its worker) and `@10x-media/automations` (registers a `webhook` trigger in the catalog).
|
|
16
|
+
- **Cross-DB**: tested on MongoDB and PostgreSQL via the matrix integration suite.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 10x Media GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# @10x-media/webhooks
|
|
2
|
+
|
|
3
|
+
Outbound webhook subscriptions for Payload v3. Opt any collection in, let subscribers register URLs in the admin panel, and deliver signed HTTP POSTs on every create/update/delete -- inline or via Payload's built-in jobs queue.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@10x-media/webhooks)
|
|
6
|
+
|
|
7
|
+
Part of the [@10x-media Payload plugins](https://github.com/10x-media/payload-plugins) collection. In beta: published under the `beta` dist-tag until a stable 1.0.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Payload v3 (peer: `payload@^3.82.0`)
|
|
12
|
+
- React 19 (peer)
|
|
13
|
+
- Node 22.18+
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @10x-media/webhooks
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { buildConfig } from 'payload'
|
|
25
|
+
import { webhooks } from '@10x-media/webhooks'
|
|
26
|
+
|
|
27
|
+
export default buildConfig({
|
|
28
|
+
plugins: [
|
|
29
|
+
webhooks({
|
|
30
|
+
collections: {
|
|
31
|
+
posts: true,
|
|
32
|
+
orders: { operations: ['create', 'delete'] },
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
That is the minimum config. The plugin adds two collections to your admin panel:
|
|
40
|
+
|
|
41
|
+
- **Webhook Subscriptions** -- admins create endpoint records (URL, events, optional secret).
|
|
42
|
+
- **Webhook Deliveries** -- an append-only log with status, response code, and a redeliver button.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
| Option | Type | Default | Description |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `disabled` | `boolean` | `false` | When `true`, returns the config unchanged. |
|
|
49
|
+
| `collections` | `Record<string, true \| CollectionWebhookConfig>` | `{}` | Collections that emit webhook events. |
|
|
50
|
+
| `subscriptions` | `CodeSubscription[]` | `[]` | Hard-coded subscriptions (no admin record needed). |
|
|
51
|
+
| `delivery` | `DeliveryMode \| DeliveryOptions` | `'auto'` | How deliveries are executed. |
|
|
52
|
+
| `subscriptionsCollection` | `{ slug?: string; hidden?: boolean }` | -- | Override the subscriptions collection slug or hide it from the admin panel. |
|
|
53
|
+
| `deliveriesLog` | `{ slug?: string; hidden?: boolean }` | -- | Override the deliveries collection slug or hide it from the admin panel. |
|
|
54
|
+
|
|
55
|
+
### `CollectionWebhookConfig`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
type CollectionWebhookConfig = {
|
|
59
|
+
operations?: ('create' | 'update' | 'delete')[]
|
|
60
|
+
includePreviousData?: boolean
|
|
61
|
+
transform?: (args: {
|
|
62
|
+
doc: Record<string, unknown>
|
|
63
|
+
previousDoc?: Record<string, unknown>
|
|
64
|
+
operation: 'create' | 'update' | 'delete'
|
|
65
|
+
req: PayloadRequest
|
|
66
|
+
}) => unknown
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`true` is shorthand for all three operations with no transform.
|
|
71
|
+
|
|
72
|
+
`transform` lets you redact fields or reshape the payload before it is sent. Return `undefined` to suppress delivery for that document.
|
|
73
|
+
|
|
74
|
+
`includePreviousData` adds a `previousData` key to the body on `update` events.
|
|
75
|
+
|
|
76
|
+
### Delivery modes
|
|
77
|
+
|
|
78
|
+
| Mode | Behavior |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `'auto'` (default) | Queued when `config.jobs.autoRun` is set or `@10x-media/jobs` is installed; inline otherwise. |
|
|
81
|
+
| `'queue'` | Always enqueued as a Payload job. A worker must run `payload.jobs.run()`. |
|
|
82
|
+
| `'inline'` | Awaited in the `afterChange`/`afterDelete` hook. Simple but adds latency to every write. |
|
|
83
|
+
|
|
84
|
+
Pass a full `DeliveryOptions` object to tune the queue and timeout:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
webhooks({
|
|
88
|
+
collections: { posts: true },
|
|
89
|
+
delivery: {
|
|
90
|
+
mode: 'queue',
|
|
91
|
+
timeoutMs: 5_000,
|
|
92
|
+
retries: 3,
|
|
93
|
+
queue: 'webhooks',
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Defaults: `timeoutMs: 10000`, `retries: 4`, `queue: 'default'`.
|
|
99
|
+
|
|
100
|
+
### Code subscriptions
|
|
101
|
+
|
|
102
|
+
Register subscriptions in code when you do not want them managed through the admin panel:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
webhooks({
|
|
106
|
+
collections: { orders: true },
|
|
107
|
+
subscriptions: [
|
|
108
|
+
{
|
|
109
|
+
id: 'my-crm',
|
|
110
|
+
url: 'https://crm.example.com/hooks/orders',
|
|
111
|
+
events: ['orders.created', 'orders.updated'],
|
|
112
|
+
secret: process.env.WEBHOOK_SECRET,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Code subscriptions are merged with admin-managed ones at delivery time.
|
|
119
|
+
|
|
120
|
+
## Webhook payload
|
|
121
|
+
|
|
122
|
+
Every delivery POSTs a JSON body:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"id": "<delivery-id>",
|
|
127
|
+
"event": "posts.created",
|
|
128
|
+
"collection": "posts",
|
|
129
|
+
"operation": "create",
|
|
130
|
+
"occurredAt": "2025-01-01T00:00:00.000Z",
|
|
131
|
+
"data": { ... }
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`previousData` is included on `update` events when `includePreviousData: true`.
|
|
136
|
+
|
|
137
|
+
## Signature verification
|
|
138
|
+
|
|
139
|
+
When a subscription has a `secret`, each request carries an `X-Webhook-Signature` header:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
X-Webhook-Signature: v1=<hex>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The signature is HMAC-SHA256 over `${timestamp}.${rawBody}`, where `timestamp` is the Unix second from the `X-Webhook-Timestamp` header.
|
|
146
|
+
|
|
147
|
+
Verify in your receiver:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import { createHmac } from 'node:crypto'
|
|
151
|
+
|
|
152
|
+
function verify(secret: string, timestamp: string, rawBody: string, signature: string): boolean {
|
|
153
|
+
const expected = createHmac('sha256', secret)
|
|
154
|
+
.update(`${timestamp}.${rawBody}`)
|
|
155
|
+
.digest('hex')
|
|
156
|
+
return signature === `v1=${expected}`
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Additional headers sent on every request: `X-Webhook-Id`, `X-Webhook-Event`, `X-Webhook-Timestamp`, `User-Agent: 10x-media-webhooks`. Subscriptions can inject extra headers via the admin panel's **Headers** array field.
|
|
161
|
+
|
|
162
|
+
## Admin panel
|
|
163
|
+
|
|
164
|
+
The plugin adds a **Webhooks** group with two collections.
|
|
165
|
+
|
|
166
|
+
**Webhook Subscriptions**: create and manage endpoint records. A random 48-character hex secret is auto-generated on create and shown in full **exactly once** on that create response -- copy it to your receiver then. On every later read it is masked (`__redacted__`); the raw value is still used internally to sign deliveries but is never returned through the API or admin again. Rotate by deleting and recreating the record.
|
|
167
|
+
|
|
168
|
+
**Webhook Deliveries**: an append-only delivery log. Each row shows the event, subscription, status, HTTP response code, and a **Redeliver** button that replays the original payload to the original URL. Access requires a logged-in admin.
|
|
169
|
+
|
|
170
|
+
## Composing with `@10x-media/jobs`
|
|
171
|
+
|
|
172
|
+
When `@10x-media/jobs` is installed, `delivery` mode auto-resolves to `'queue'` and the delivery task runs under the jobs worker. No extra config is needed -- the plugin detects the sibling plugin automatically.
|
|
173
|
+
|
|
174
|
+
## Composing with `@10x-media/automations`
|
|
175
|
+
|
|
176
|
+
When `@10x-media/automations` is installed, the plugin contributes a `webhook` trigger slug to the automations catalog, reserving it for future inbound-webhook support. This plugin is outbound-only today and does not yet fire that trigger -- the contribution simply registers the slug so a later inbound phase can use it. No extra config is needed.
|
|
177
|
+
|
|
178
|
+
## Security
|
|
179
|
+
|
|
180
|
+
- **Signing secrets are reveal-once.** A subscription's secret is shown in full only on the create response and masked (`__redacted__`) on every read thereafter; the raw value is used internally to sign deliveries and is never returned via REST, GraphQL, or the admin again. It is currently stored unencrypted at rest -- encryption-at-rest is planned.
|
|
181
|
+
- **Outbound requests target operator-supplied URLs (SSRF).** Deliveries POST to whatever URL a subscription specifies, including private or internal hosts. Subscriptions are created by authenticated admins, so treat that as a trusted operation; if your admins are not fully trusted, restrict outbound egress at the network layer or front your receivers with an allowlist.
|
|
182
|
+
- **Admin access is the trust boundary.** Both collections require a logged-in user, and the redeliver endpoint authorizes by login only (any authenticated user may redeliver any delivery). Tighten the collections with your own access control if you need finer-grained permissions.
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
[MIT](./LICENSE). Copyright 10x Media GmbH.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DefaultCellComponentProps } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/delivery/DeliveryStatusCell.d.ts
|
|
4
|
+
/** List cell rendering a delivery's status as a native Payload Pill. */
|
|
5
|
+
declare const DeliveryStatusCell: ({
|
|
6
|
+
cellData
|
|
7
|
+
}: DefaultCellComponentProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/delivery/RedeliverButton.d.ts
|
|
10
|
+
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
11
|
+
declare const RedeliverButton: () => import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DeliveryStatusCell, RedeliverButton };
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { t as keys } from "../keys-BdNiD3rC.js";
|
|
3
|
+
import { Button, Pill, toast, useConfig, useDocumentInfo, useTranslation } from "@payloadcms/ui";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
//#region src/translations/useTranslation.ts
|
|
7
|
+
/**
|
|
8
|
+
* `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without
|
|
9
|
+
* a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.
|
|
10
|
+
*/
|
|
11
|
+
const useTranslation$1 = () => useTranslation();
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/delivery/DeliveryStatusCell.tsx
|
|
14
|
+
const META = {
|
|
15
|
+
pending: {
|
|
16
|
+
labelKey: keys.statusPending,
|
|
17
|
+
pillStyle: "light"
|
|
18
|
+
},
|
|
19
|
+
success: {
|
|
20
|
+
labelKey: keys.statusSuccess,
|
|
21
|
+
pillStyle: "success"
|
|
22
|
+
},
|
|
23
|
+
failed: {
|
|
24
|
+
labelKey: keys.statusFailed,
|
|
25
|
+
pillStyle: "warning"
|
|
26
|
+
},
|
|
27
|
+
dead: {
|
|
28
|
+
labelKey: keys.statusDead,
|
|
29
|
+
pillStyle: "error"
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const FALLBACK = {
|
|
33
|
+
labelKey: keys.statusPending,
|
|
34
|
+
pillStyle: "light"
|
|
35
|
+
};
|
|
36
|
+
/** List cell rendering a delivery's status as a native Payload Pill. */
|
|
37
|
+
const DeliveryStatusCell = ({ cellData }) => {
|
|
38
|
+
const { t } = useTranslation$1();
|
|
39
|
+
const meta = META[String(cellData)] ?? FALLBACK;
|
|
40
|
+
return /* @__PURE__ */ jsx(Pill, {
|
|
41
|
+
pillStyle: meta.pillStyle,
|
|
42
|
+
size: "small",
|
|
43
|
+
children: t(meta.labelKey)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/delivery/RedeliverButton.tsx
|
|
48
|
+
/** Doc-view action that POSTs to the deliveries redeliver endpoint. */
|
|
49
|
+
const RedeliverButton = () => {
|
|
50
|
+
const { id, collectionSlug } = useDocumentInfo();
|
|
51
|
+
const { config } = useConfig();
|
|
52
|
+
const { t } = useTranslation$1();
|
|
53
|
+
const [busy, setBusy] = useState(false);
|
|
54
|
+
if (!id || !collectionSlug) return null;
|
|
55
|
+
const apiRoute = config.routes?.api ?? "/api";
|
|
56
|
+
const serverURL = config.serverURL ?? "";
|
|
57
|
+
const onClick = async () => {
|
|
58
|
+
setBusy(true);
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`${serverURL}${apiRoute}/${collectionSlug}/${encodeURIComponent(String(id))}/redeliver`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
credentials: "include"
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
65
|
+
toast.success(t(keys.redeliverDone));
|
|
66
|
+
} catch {
|
|
67
|
+
toast.error(t(keys.redeliver));
|
|
68
|
+
} finally {
|
|
69
|
+
setBusy(false);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
73
|
+
buttonStyle: "secondary",
|
|
74
|
+
disabled: busy,
|
|
75
|
+
onClick,
|
|
76
|
+
size: "small",
|
|
77
|
+
children: t(keys.redeliver)
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
//#endregion
|
|
81
|
+
export { DeliveryStatusCell, RedeliverButton };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useTranslation","usePayloadTranslation","useTranslation","useTranslation"],"sources":["../../src/translations/useTranslation.ts","../../src/delivery/DeliveryStatusCell.tsx","../../src/delivery/RedeliverButton.tsx"],"sourcesContent":["'use client'\n\nimport { useTranslation as usePayloadTranslation } from '@payloadcms/ui'\n\nimport type { TranslationKey } from './keys'\n\n/**\n * `useTranslation` bound to this plugin's keys, so `t(keys.X)` typechecks without\n * a per-call `@ts-expect-error`. Returns Payload's `{ t, i18n }` unchanged.\n */\nexport const useTranslation = () => usePayloadTranslation<Record<string, never>, TranslationKey>()\n","'use client'\n\nimport { Pill } from '@payloadcms/ui'\nimport type { DefaultCellComponentProps } from 'payload'\n\nimport { keys, type TranslationKey } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\ntype MetaEntry = { labelKey: TranslationKey; pillStyle: 'error' | 'light' | 'success' | 'warning' }\n\nconst META: Record<string, MetaEntry> = {\n\tpending: { labelKey: keys.statusPending, pillStyle: 'light' },\n\tsuccess: { labelKey: keys.statusSuccess, pillStyle: 'success' },\n\tfailed: { labelKey: keys.statusFailed, pillStyle: 'warning' },\n\tdead: { labelKey: keys.statusDead, pillStyle: 'error' },\n}\n\nconst FALLBACK: MetaEntry = { labelKey: keys.statusPending, pillStyle: 'light' }\n\n/** List cell rendering a delivery's status as a native Payload Pill. */\nexport const DeliveryStatusCell = ({ cellData }: DefaultCellComponentProps) => {\n\tconst { t } = useTranslation()\n\tconst meta = META[String(cellData)] ?? FALLBACK\n\treturn (\n\t\t<Pill pillStyle={meta.pillStyle} size=\"small\">\n\t\t\t{t(meta.labelKey)}\n\t\t</Pill>\n\t)\n}\n","'use client'\n\nimport { Button, toast, useConfig, useDocumentInfo } from '@payloadcms/ui'\nimport { useState } from 'react'\n\nimport { keys } from '../translations/keys'\nimport { useTranslation } from '../translations/useTranslation'\n\n/** Doc-view action that POSTs to the deliveries redeliver endpoint. */\nexport const RedeliverButton = () => {\n\tconst { id, collectionSlug } = useDocumentInfo()\n\tconst { config } = useConfig()\n\tconst { t } = useTranslation()\n\tconst [busy, setBusy] = useState(false)\n\n\tif (!id || !collectionSlug) {\n\t\treturn null\n\t}\n\n\tconst apiRoute = config.routes?.api ?? '/api'\n\tconst serverURL = config.serverURL ?? ''\n\n\tconst onClick = async () => {\n\t\tsetBusy(true)\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`${serverURL}${apiRoute}/${collectionSlug}/${encodeURIComponent(String(id))}/redeliver`,\n\t\t\t\t{ method: 'POST', credentials: 'include' }\n\t\t\t)\n\t\t\tif (!res.ok) {\n\t\t\t\tthrow new Error(String(res.status))\n\t\t\t}\n\t\t\ttoast.success(t(keys.redeliverDone))\n\t\t} catch {\n\t\t\ttoast.error(t(keys.redeliver))\n\t\t} finally {\n\t\t\tsetBusy(false)\n\t\t}\n\t}\n\n\treturn (\n\t\t<Button buttonStyle=\"secondary\" disabled={busy} onClick={onClick} size=\"small\">\n\t\t\t{t(keys.redeliver)}\n\t\t</Button>\n\t)\n}\n"],"mappings":";;;;;;;;;;AAUA,MAAaA,yBAAuBC,eAA6D;;;ACAjG,MAAM,OAAkC;CACvC,SAAS;EAAE,UAAU,KAAK;EAAe,WAAW;CAAQ;CAC5D,SAAS;EAAE,UAAU,KAAK;EAAe,WAAW;CAAU;CAC9D,QAAQ;EAAE,UAAU,KAAK;EAAc,WAAW;CAAU;CAC5D,MAAM;EAAE,UAAU,KAAK;EAAY,WAAW;CAAQ;AACvD;AAEA,MAAM,WAAsB;CAAE,UAAU,KAAK;CAAe,WAAW;AAAQ;;AAG/E,MAAa,sBAAsB,EAAE,eAA0C;CAC9E,MAAM,EAAE,MAAMC,iBAAe;CAC7B,MAAM,OAAO,KAAK,OAAO,QAAQ,MAAM;CACvC,OACC,oBAAC,MAAD;EAAM,WAAW,KAAK;EAAW,MAAK;YACpC,EAAE,KAAK,QAAQ;CACX,CAAA;AAER;;;;ACnBA,MAAa,wBAAwB;CACpC,MAAM,EAAE,IAAI,mBAAmB,gBAAgB;CAC/C,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,EAAE,MAAMC,iBAAe;CAC7B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,IAAI,CAAC,MAAM,CAAC,gBACX,OAAO;CAGR,MAAM,WAAW,OAAO,QAAQ,OAAO;CACvC,MAAM,YAAY,OAAO,aAAa;CAEtC,MAAM,UAAU,YAAY;EAC3B,QAAQ,IAAI;EACZ,IAAI;GACH,MAAM,MAAM,MAAM,MACjB,GAAG,YAAY,SAAS,GAAG,eAAe,GAAG,mBAAmB,OAAO,EAAE,CAAC,EAAE,aAC5E;IAAE,QAAQ;IAAQ,aAAa;GAAU,CAC1C;GACA,IAAI,CAAC,IAAI,IACR,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;GAEnC,MAAM,QAAQ,EAAE,KAAK,aAAa,CAAC;EACpC,QAAQ;GACP,MAAM,MAAM,EAAE,KAAK,SAAS,CAAC;EAC9B,UAAU;GACT,QAAQ,KAAK;EACd;CACD;CAEA,OACC,oBAAC,QAAD;EAAQ,aAAY;EAAY,UAAU;EAAe;EAAS,MAAK;YACrE,EAAE,KAAK,SAAS;CACV,CAAA;AAEV"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/translations/keys.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Typed translation keys. Lookups must go through these constants, not string
|
|
4
|
+
* literals. Every key here must have a value in every locale (`en.ts`), or it is
|
|
5
|
+
* a type error.
|
|
6
|
+
*/
|
|
7
|
+
declare const keys: {
|
|
8
|
+
readonly pluginName: "webhooks:pluginName";
|
|
9
|
+
readonly subscriptionSingular: "webhooks:subscriptionSingular";
|
|
10
|
+
readonly subscriptionPlural: "webhooks:subscriptionPlural";
|
|
11
|
+
readonly deliverySingular: "webhooks:deliverySingular";
|
|
12
|
+
readonly deliveryPlural: "webhooks:deliveryPlural";
|
|
13
|
+
readonly fieldName: "webhooks:fieldName";
|
|
14
|
+
readonly fieldUrl: "webhooks:fieldUrl";
|
|
15
|
+
readonly fieldEnabled: "webhooks:fieldEnabled";
|
|
16
|
+
readonly fieldEvents: "webhooks:fieldEvents";
|
|
17
|
+
readonly fieldSecret: "webhooks:fieldSecret";
|
|
18
|
+
readonly fieldSecretHelp: "webhooks:fieldSecretHelp";
|
|
19
|
+
readonly fieldHeaders: "webhooks:fieldHeaders";
|
|
20
|
+
readonly fieldDescription: "webhooks:fieldDescription";
|
|
21
|
+
readonly statusPending: "webhooks:statusPending";
|
|
22
|
+
readonly statusSuccess: "webhooks:statusSuccess";
|
|
23
|
+
readonly statusFailed: "webhooks:statusFailed";
|
|
24
|
+
readonly statusDead: "webhooks:statusDead";
|
|
25
|
+
readonly redeliver: "webhooks:redeliver";
|
|
26
|
+
readonly redeliverDone: "webhooks:redeliverDone";
|
|
27
|
+
};
|
|
28
|
+
type TranslationKey = (typeof keys)[keyof typeof keys];
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/translations/index.d.ts
|
|
31
|
+
/** Per-locale messages merged into `config.i18n.translations`. English only for now. */
|
|
32
|
+
declare const translations: {
|
|
33
|
+
en: Record<string, Record<string, string>>;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { type TranslationKey as WebhooksTranslationKeys, keys, translations };
|
|
37
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { PayloadRequest } from "payload";
|
|
2
|
+
|
|
3
|
+
//#region src/options.d.ts
|
|
4
|
+
type WebhookOperation = 'create' | 'update' | 'delete';
|
|
5
|
+
type CollectionWebhookConfig = {
|
|
6
|
+
operations?: WebhookOperation[];
|
|
7
|
+
includePreviousData?: boolean;
|
|
8
|
+
transform?: (args: {
|
|
9
|
+
doc: Record<string, unknown>;
|
|
10
|
+
previousDoc?: Record<string, unknown>;
|
|
11
|
+
operation: WebhookOperation;
|
|
12
|
+
req: PayloadRequest;
|
|
13
|
+
}) => unknown;
|
|
14
|
+
};
|
|
15
|
+
type CodeSubscription = {
|
|
16
|
+
id: string;
|
|
17
|
+
url: string;
|
|
18
|
+
events: string[];
|
|
19
|
+
secret?: string;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
};
|
|
23
|
+
type DeliveryMode = 'auto' | 'queue' | 'inline';
|
|
24
|
+
type DeliveryOptions = {
|
|
25
|
+
mode?: DeliveryMode;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
retries?: number;
|
|
28
|
+
queue?: string;
|
|
29
|
+
};
|
|
30
|
+
type WebhooksPluginOptions = {
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
collections?: Record<string, true | CollectionWebhookConfig>;
|
|
33
|
+
subscriptions?: CodeSubscription[];
|
|
34
|
+
delivery?: DeliveryMode | DeliveryOptions;
|
|
35
|
+
subscriptionsCollection?: {
|
|
36
|
+
slug?: string;
|
|
37
|
+
hidden?: boolean;
|
|
38
|
+
};
|
|
39
|
+
deliveriesLog?: {
|
|
40
|
+
slug?: string;
|
|
41
|
+
hidden?: boolean;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/index.d.ts
|
|
46
|
+
declare module 'payload' {
|
|
47
|
+
interface RegisteredPlugins {
|
|
48
|
+
'@10x-media/webhooks': WebhooksPluginOptions;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** The trigger slug webhooks contributes to the automations catalog. */
|
|
52
|
+
declare const WEBHOOK_TRIGGER_SLUG: "webhook";
|
|
53
|
+
/**
|
|
54
|
+
* Webhooks plugin for Payload v3. Runs before automations (`order: 10`) so it can
|
|
55
|
+
* push its `webhook` trigger into automations when present, and builds outbound
|
|
56
|
+
* delivery: opt-in collections emit signed HTTP POSTs to subscribed endpoints,
|
|
57
|
+
* delivered via native Payload jobs or bounded-await inline.
|
|
58
|
+
*/
|
|
59
|
+
declare const webhooks: (options: WebhooksPluginOptions) => import("payload").Plugin;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { webhooks as n, WebhooksPluginOptions as r, WEBHOOK_TRIGGER_SLUG as t };
|
|
62
|
+
//# sourceMappingURL=index-CpsTtHO1.d.ts.map
|
package/dist/index.d.ts
ADDED