@forumulate/sdk 0.7.0 → 0.10.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.
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The policy acceptance gate, from your server: `client.policies`.
3
+ *
4
+ * status what this user still has to accept before this event
5
+ * accept record an acceptance your own UI collected (server-verified)
6
+ * claim redeem a sign-up receipt for the account you just created
7
+ * ticket mint a ticket online (when your server holds a PAT, not the gate key)
8
+ * verify check an acceptance token online (offline: `verifyAcceptanceToken`)
9
+ * acceptances the acceptance log; `export` as CSV or JSON
10
+ * erase a data-subject request: delete one person's acceptances
11
+ * list the policies themselves, from the policy feed (signed or public)
12
+ * release one exact published version, for ever
13
+ *
14
+ * Hand-written over the transport, like `./translations`, because the
15
+ * generated `schema.ts` is refreshed from the DEPLOYED spec and these routes
16
+ * ship with this release. The shapes are the contract in
17
+ * `packages/shared/src/policy-gate/dtos.ts`, restated: the SDK has no runtime
18
+ * dependencies, so it cannot import them.
19
+ */
20
+ import type { PolicyFeedList } from './policy-feed';
21
+ import type { PolicyGateEvent } from './policy-tokens';
22
+ type Send = <T>(method: string, path: string, opts?: {
23
+ body?: unknown;
24
+ query?: Record<string, string | number | boolean | undefined>;
25
+ headers?: Record<string, string>;
26
+ kind?: 'json' | 'text';
27
+ maxRetries?: number;
28
+ }) => Promise<T>;
29
+ export type PolicyPresentation = 'checkbox' | 'checkbox-modal' | 'modal' | 'inline-scroll' | 'page' | 'link';
30
+ export type PolicyGateReason = 'sign_up' | 'never_accepted' | 'new_version' | 'grace_period' | 'grace_expired' | 'accepted' | 'reaccept_never' | 'not_for_event' | 'not_yet_effective';
31
+ /** One policy's verdict for a subject and event. */
32
+ export interface PolicyGateStatusItem {
33
+ key: string;
34
+ title: string;
35
+ /** Show it now. */
36
+ ask: boolean;
37
+ /** Withhold the event until it is accepted. */
38
+ blocking: boolean;
39
+ reason: PolicyGateReason;
40
+ required: boolean;
41
+ version: string | null;
42
+ effectiveAt: string | null;
43
+ contentUrl: string | null;
44
+ /** SHA-256 (hex) of `contentHtml`: send it back when recording the acceptance. */
45
+ contentSha256: string | null;
46
+ /** With `includeContent: true`. */
47
+ contentHtml?: string | null;
48
+ summaryOfChanges: string | null;
49
+ acceptedVersion: string | null;
50
+ /** When a grace period ends. */
51
+ dueAt: string | null;
52
+ /** A future version that can be accepted early. */
53
+ upcoming: {
54
+ version: string;
55
+ effectiveAt: string;
56
+ } | null;
57
+ presentation: PolicyPresentation;
58
+ requireScroll: boolean;
59
+ showSummary: boolean;
60
+ decline: 'allow' | 'block';
61
+ dismissible: boolean;
62
+ reaccept: 'never' | 'next_sign_in' | 'grace_days';
63
+ events: PolicyGateEvent[];
64
+ /** Which level decided each behaviour field: `call`, `space`, `org`, `policy` or `default`. */
65
+ sources: Record<string, string>;
66
+ }
67
+ export interface PolicyGateStatus {
68
+ enabled: boolean;
69
+ subject: string | null;
70
+ event: PolicyGateEvent;
71
+ /** Nothing blocks the event. */
72
+ satisfied: boolean;
73
+ /** Keys still blocking. */
74
+ pending: string[];
75
+ /** Keys asked but not required. */
76
+ optional: string[];
77
+ policies: PolicyGateStatusItem[];
78
+ /** The database clock the rules were evaluated at. */
79
+ evaluatedAt: string;
80
+ }
81
+ /** One policy being accepted: which version, the hash of what was shown, and how. */
82
+ export interface PolicyAcceptanceItem {
83
+ key: string;
84
+ version: string;
85
+ contentSha256: string;
86
+ presentation: PolicyPresentation;
87
+ scrolledToEnd?: boolean;
88
+ }
89
+ export interface PolicyAcceptanceRow {
90
+ id: string;
91
+ spaceId: string;
92
+ subject: string;
93
+ subjectEmailHash: string | null;
94
+ key: string;
95
+ version: string;
96
+ contentSha256: string;
97
+ presentation: PolicyPresentation;
98
+ display: 'checkbox' | 'scroll' | 'link';
99
+ scrolledToEnd: boolean;
100
+ event: PolicyGateEvent | null;
101
+ /** `server`, `ticket`, `receipt` or `unverified`. */
102
+ verification: string;
103
+ sourceApp: string | null;
104
+ locale: string | null;
105
+ ip: string | null;
106
+ userAgent: string | null;
107
+ batchId: string;
108
+ acceptedAt: string;
109
+ }
110
+ export interface PolicyAcceptanceResult {
111
+ acceptances: PolicyAcceptanceRow[];
112
+ /** A signed acceptance token for the subject. */
113
+ token: string;
114
+ expiresAt: string;
115
+ satisfied: boolean;
116
+ pending: string[];
117
+ }
118
+ /** Email, or its hash: who the subject is, for a later data-subject request. */
119
+ export interface PolicySubjectIdentity {
120
+ /** Hashed on arrival, never stored. */
121
+ email?: string;
122
+ /** SHA-256 hex of the trimmed, lower-cased email (`policyEmailHash`). */
123
+ emailHash?: string;
124
+ }
125
+ export interface PolicyGateSpace {
126
+ orgId: string;
127
+ /** The Legal space whose published policies gate your app. */
128
+ spaceId: string;
129
+ }
130
+ export interface PolicyStatusInput extends PolicyGateSpace {
131
+ /** Your id for the user. Omit on a sign-up: everything is asked. */
132
+ subject?: string;
133
+ event: PolicyGateEvent;
134
+ /** Include each version's `contentHtml` (to show it in your own UI). */
135
+ includeContent?: boolean;
136
+ }
137
+ export interface PolicyAcceptInput extends PolicyGateSpace, PolicySubjectIdentity {
138
+ subject: string;
139
+ event?: PolicyGateEvent;
140
+ policies: PolicyAcceptanceItem[];
141
+ /** Recorded as `sourceApp` (`web`, `ios`). */
142
+ sourceApp?: string;
143
+ /** The END USER's IP, which your server saw and Forumulate did not. */
144
+ ip?: string;
145
+ /** The END USER's user agent. */
146
+ userAgent?: string;
147
+ locale?: string;
148
+ }
149
+ export interface PolicyListInput {
150
+ /** The Legal space. */
151
+ spaceId: string;
152
+ /** One policy only. */
153
+ key?: string;
154
+ /**
155
+ * The link's shared secret (`whsec_…`) from **Policy sources**: reads the
156
+ * signed feed. Without it, the public feed is read (404 unless the space
157
+ * turned it on).
158
+ */
159
+ secret?: string;
160
+ /** Sign with `atlas-*` headers at the Atlas address instead. */
161
+ prefix?: 'forumulate' | 'atlas';
162
+ }
163
+ /**
164
+ * The acceptance items for every policy a status asks for — what to send to
165
+ * `accept` once your own UI has shown them. `presentation` is how YOU showed
166
+ * them; it is recorded as such.
167
+ */
168
+ export declare function acceptanceItemsFrom(status: Pick<PolicyGateStatus, 'policies'>, shown: {
169
+ presentation: PolicyPresentation;
170
+ scrolledToEnd?: boolean;
171
+ keys?: string[];
172
+ }): PolicyAcceptanceItem[];
173
+ export declare function policiesApi(send: Send, baseUrl: () => string): {
174
+ /** What `subject` still has to accept before `event`. `satisfied` false = withhold the event. */
175
+ status: (input: PolicyStatusInput) => Promise<PolicyGateStatus>;
176
+ /** Record what your own UI collected. Answers with a signed acceptance token. */
177
+ accept: (input: PolicyAcceptInput) => Promise<PolicyAcceptanceResult>;
178
+ /** Redeem a sign-up receipt for the account you just created. Once only. */
179
+ claim: (input: PolicyGateSpace & PolicySubjectIdentity & {
180
+ receipt: string;
181
+ subject: string;
182
+ }) => Promise<PolicyAcceptanceResult>;
183
+ /**
184
+ * Mint a ticket online. Signing it yourself with `signPolicyTicket` needs
185
+ * no call; this is for a server that holds a PAT but not the gate key.
186
+ */
187
+ ticket: (input: PolicyGateSpace & PolicySubjectIdentity & {
188
+ subject: string;
189
+ event: PolicyGateEvent;
190
+ sourceApp?: string;
191
+ returnUrl?: string;
192
+ state?: string;
193
+ locale?: string;
194
+ ttlSeconds?: number;
195
+ }) => Promise<{
196
+ ticket: string;
197
+ expiresAt: string;
198
+ pageUrl: string;
199
+ }>;
200
+ /** Verify a token online. Offline (no round trip): `verifyAcceptanceToken`. */
201
+ verify: (input: PolicyGateSpace & {
202
+ token: string;
203
+ subject?: string;
204
+ }) => Promise<{
205
+ valid: boolean;
206
+ reason?: string;
207
+ claims?: Record<string, unknown>;
208
+ }>;
209
+ /** The acceptance log, newest first; pass `page.nextCursor` back as `cursor`. */
210
+ acceptances: (input: PolicyGateSpace & PolicySubjectIdentity & {
211
+ subject?: string;
212
+ key?: string;
213
+ cursor?: string;
214
+ limit?: number;
215
+ }) => Promise<{
216
+ acceptances: PolicyAcceptanceRow[];
217
+ page: {
218
+ limit: number;
219
+ hasMore: boolean;
220
+ nextCursor: string | null;
221
+ };
222
+ }>;
223
+ /** The log as a file (CSV or JSON text). */
224
+ export: (input: PolicyGateSpace & {
225
+ format: "csv" | "json";
226
+ subject?: string;
227
+ emailHash?: string;
228
+ key?: string;
229
+ }) => Promise<string>;
230
+ /** A data-subject erasure: every acceptance of one person, across the org's spaces. */
231
+ erase: (input: {
232
+ orgId: string;
233
+ } & PolicySubjectIdentity & {
234
+ subject?: string;
235
+ }) => Promise<{
236
+ erased: number;
237
+ }>;
238
+ /**
239
+ * The policies, from Forumulate policy feed v1. With `secret`, the signed
240
+ * feed (`/policy-feed/spaces/{id}/policies`, or Atlas's address with
241
+ * `prefix: 'atlas'`); without, the public one.
242
+ */
243
+ list: (input: PolicyListInput) => Promise<PolicyFeedList>;
244
+ /** One exact published version — the text behind its `content_url`, which never changes. */
245
+ release: (input: {
246
+ orgId: string;
247
+ key: string;
248
+ version: string;
249
+ }) => Promise<{
250
+ release: {
251
+ kind: string;
252
+ version: string;
253
+ title: string;
254
+ contentHtml: string;
255
+ contentSha256: string;
256
+ publishedAt: string;
257
+ effectiveAt: string;
258
+ changeNote: string | null;
259
+ };
260
+ }>;
261
+ };
262
+ export type PoliciesApi = ReturnType<typeof policiesApi>;
263
+ export {};
264
+ //# sourceMappingURL=policies.d.ts.map
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.acceptanceItemsFrom = acceptanceItemsFrom;
4
+ exports.policiesApi = policiesApi;
5
+ const policy_feed_1 = require("./policy-feed");
6
+ /**
7
+ * The acceptance items for every policy a status asks for — what to send to
8
+ * `accept` once your own UI has shown them. `presentation` is how YOU showed
9
+ * them; it is recorded as such.
10
+ */
11
+ function acceptanceItemsFrom(status, shown) {
12
+ return status.policies
13
+ .filter((p) => p.ask && p.version && p.contentSha256)
14
+ .filter((p) => !shown.keys || shown.keys.includes(p.key))
15
+ .map((p) => ({
16
+ key: p.key,
17
+ version: p.version,
18
+ contentSha256: p.contentSha256,
19
+ presentation: shown.presentation,
20
+ ...(shown.scrolledToEnd === undefined ? {} : { scrolledToEnd: shown.scrolledToEnd }),
21
+ }));
22
+ }
23
+ const e = encodeURIComponent;
24
+ function policiesApi(send, baseUrl) {
25
+ const gate = (s) => `/orgs/${e(s.orgId)}/spaces/${e(s.spaceId)}/policy-gate`;
26
+ const strip = (input) => {
27
+ const { orgId: _o, spaceId: _s, ...rest } = input;
28
+ return rest;
29
+ };
30
+ return {
31
+ /** What `subject` still has to accept before `event`. `satisfied` false = withhold the event. */
32
+ status: (input) => send('GET', `${gate(input)}/status`, {
33
+ query: {
34
+ subject: input.subject,
35
+ event: input.event,
36
+ include: input.includeContent ? 'content' : undefined,
37
+ },
38
+ }),
39
+ /** Record what your own UI collected. Answers with a signed acceptance token. */
40
+ accept: (input) => send('POST', `${gate(input)}/acceptances`, { body: strip(input) }),
41
+ /** Redeem a sign-up receipt for the account you just created. Once only. */
42
+ claim: (input) => send('POST', `${gate(input)}/acceptances/claim`, { body: strip(input) }),
43
+ /**
44
+ * Mint a ticket online. Signing it yourself with `signPolicyTicket` needs
45
+ * no call; this is for a server that holds a PAT but not the gate key.
46
+ */
47
+ ticket: (input) => send('POST', `${gate(input)}/tickets`, {
48
+ body: strip(input),
49
+ }),
50
+ /** Verify a token online. Offline (no round trip): `verifyAcceptanceToken`. */
51
+ verify: (input) => send('POST', `${gate(input)}/tokens/verify`, { body: strip(input) }),
52
+ /** The acceptance log, newest first; pass `page.nextCursor` back as `cursor`. */
53
+ acceptances: (input) => send('GET', `${gate(input)}/acceptances`, { query: strip(input) }),
54
+ /** The log as a file (CSV or JSON text). */
55
+ export: (input) => send('GET', `${gate(input)}/acceptances/export`, {
56
+ query: strip(input),
57
+ kind: 'text',
58
+ }),
59
+ /** A data-subject erasure: every acceptance of one person, across the org's spaces. */
60
+ erase: (input) => {
61
+ const { orgId, ...body } = input;
62
+ return send('POST', `/orgs/${e(orgId)}/policy-gate/subjects/erase`, { body });
63
+ },
64
+ /**
65
+ * The policies, from Forumulate policy feed v1. With `secret`, the signed
66
+ * feed (`/policy-feed/spaces/{id}/policies`, or Atlas's address with
67
+ * `prefix: 'atlas'`); without, the public one.
68
+ */
69
+ list: async (input) => {
70
+ const q = input.key ? `?key=${e(input.key)}` : '';
71
+ if (!input.secret) {
72
+ return send('GET', `/public/spaces/${e(input.spaceId)}/policies${q}`);
73
+ }
74
+ const route = input.prefix === 'atlas'
75
+ ? `/atlas/spaces/${e(input.spaceId)}/policies${q}`
76
+ : `/policy-feed/spaces/${e(input.spaceId)}/policies${q}`;
77
+ // The signature covers the path AS REQUESTED, so the base URL's own path
78
+ // (`/api` on forumulate.com) is part of it.
79
+ const prefix = new URL(baseUrl()).pathname.replace(/\/+$/, '');
80
+ const headers = await (0, policy_feed_1.signPolicyFeedRequest)({
81
+ secret: input.secret,
82
+ path: `${prefix}${route}`,
83
+ prefix: input.prefix,
84
+ });
85
+ // No retries: the request id is single-use, so a retry of a request that
86
+ // reached the server is refused as a replay. Call again to re-sign.
87
+ return send('GET', route, { headers, maxRetries: 0 });
88
+ },
89
+ /** One exact published version — the text behind its `content_url`, which never changes. */
90
+ release: (input) => send('GET', `/public/orgs/${e(input.orgId)}/legal/${e(input.key)}/releases/${e(input.version)}`),
91
+ };
92
+ }
93
+ //# sourceMappingURL=policies.js.map
@@ -0,0 +1,5 @@
1
+ /** Byte helpers the policy files share. Internal: not re-exported from the package. */
2
+ export declare function base64UrlEncode(bytes: Uint8Array): string;
3
+ export declare function base64UrlDecode(s: string): Uint8Array;
4
+ export declare function equalBytes(a: Uint8Array, b: Uint8Array): boolean;
5
+ //# sourceMappingURL=policy-crypto.d.ts.map
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /** Byte helpers the policy files share. Internal: not re-exported from the package. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.base64UrlEncode = base64UrlEncode;
5
+ exports.base64UrlDecode = base64UrlDecode;
6
+ exports.equalBytes = equalBytes;
7
+ function base64UrlEncode(bytes) {
8
+ let s = '';
9
+ for (const b of bytes)
10
+ s += String.fromCharCode(b);
11
+ return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
12
+ }
13
+ function base64UrlDecode(s) {
14
+ if (!/^[A-Za-z0-9_-]*$/.test(s))
15
+ throw new TypeError('not base64url');
16
+ const pad = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4);
17
+ const bin = atob(pad);
18
+ const out = new Uint8Array(bin.length);
19
+ for (let i = 0; i < bin.length; i++)
20
+ out[i] = bin.charCodeAt(i);
21
+ return out;
22
+ }
23
+ function equalBytes(a, b) {
24
+ if (a.length !== b.length)
25
+ return false;
26
+ let diff = 0;
27
+ for (let i = 0; i < a.length; i++)
28
+ diff |= a[i] ^ b[i];
29
+ return diff === 0;
30
+ }
31
+ //# sourceMappingURL=policy-crypto.js.map
@@ -0,0 +1,139 @@
1
+ /** One version in the feed. Field names are the protocol's (snake_case). */
2
+ export interface PolicyFeedVersion {
3
+ version: string;
4
+ published_at: string;
5
+ effective_at: string;
6
+ content_html?: string;
7
+ content_markdown?: string;
8
+ /** Public URL of exactly this version; it never changes. */
9
+ content_url: string;
10
+ /** SHA-256 hex of `content_html`, so a consumer can check what it stored. */
11
+ content_sha256?: string;
12
+ summary_of_changes?: string;
13
+ locale?: string;
14
+ }
15
+ export interface PolicyFeedPolicy {
16
+ key: string;
17
+ title: string;
18
+ display: 'checkbox' | 'scroll' | 'link';
19
+ required: boolean;
20
+ applies_to: 'sign_up' | 'sign_in_and_sign_up';
21
+ /** `never`, `next_sign_in` or `grace_days:N`. */
22
+ reaccept?: string;
23
+ versions: PolicyFeedVersion[];
24
+ }
25
+ export interface PolicyFeedList {
26
+ object: 'policy_list';
27
+ policies: PolicyFeedPolicy[];
28
+ }
29
+ export type PolicyWebhookEvent = {
30
+ type: 'policy.published';
31
+ key: string;
32
+ version: string;
33
+ occurred_at: string;
34
+ } | {
35
+ type: 'policy.retired';
36
+ key: string;
37
+ occurred_at: string;
38
+ };
39
+ /** The HMAC key a secret stands for: the decoded bytes of a `whsec_` secret, else its UTF-8. */
40
+ export declare function policySecretKey(secret: string): Uint8Array;
41
+ /** Headers as a Fetch `Headers`, a Node `IncomingHttpHeaders` or a plain record. */
42
+ export type HeaderBag = Headers | Record<string, string | string[] | undefined | null> | {
43
+ get(name: string): string | null;
44
+ };
45
+ /** The exact string a feed request signs. */
46
+ export declare function policyFeedSigningString(id: string, timestamp: string, path: string): string;
47
+ export interface SignPolicyFeedRequestInput {
48
+ /** The link's shared secret (`whsec_…`). During a rotation, pass both: every one signs. */
49
+ secret: string | readonly string[];
50
+ /** Path plus query as you will request it, e.g. `/api/policy-feed/spaces/sp_1/policies?key=tos`. */
51
+ path: string;
52
+ /** Unique per request; the source refuses a repeat within five minutes. Default: random. */
53
+ id?: string;
54
+ /** Unix milliseconds. Default: now. */
55
+ timestamp?: number;
56
+ /** `forumulate` (default) or `atlas`, for the header names. */
57
+ prefix?: 'forumulate' | 'atlas';
58
+ }
59
+ /**
60
+ * The three headers that sign one feed request. Use a fresh call per request:
61
+ * the id is single-use, so a retry must be signed again.
62
+ *
63
+ * ```ts
64
+ * const path = `/api/policy-feed/spaces/${spaceId}/policies`;
65
+ * const headers = await signPolicyFeedRequest({ secret: process.env.POLICY_FEED_SECRET!, path });
66
+ * const list = await (await fetch(`https://forumulate.com${path}`, { headers })).json();
67
+ * ```
68
+ */
69
+ export declare function signPolicyFeedRequest(input: SignPolicyFeedRequestInput): Promise<Record<string, string>>;
70
+ export type PolicyFeedRequestFailure = 'missing_headers' | 'stale_timestamp' | 'bad_signature' | 'replayed';
71
+ export interface VerifyPolicyFeedRequestInput {
72
+ /** Every secret still valid for the link: the current one and, for 24 h after a rotation, the previous. */
73
+ secrets: string | readonly string[];
74
+ headers: HeaderBag;
75
+ /**
76
+ * The path plus query the CONSUMER requested. Pass several when a proxy in
77
+ * front of you rewrites it (`/api/…` outside, `/…` inside): any one matching
78
+ * is enough.
79
+ */
80
+ path: string | readonly string[];
81
+ /** Milliseconds of clock difference allowed. Default 300 000 (five minutes). */
82
+ toleranceMs?: number;
83
+ /**
84
+ * Replay protection: return `true` if this id was already seen in the last
85
+ * five minutes (and remember it). Without it, a captured request can be
86
+ * replayed inside the window — fine for a read-only feed, but say so.
87
+ */
88
+ seen?: (id: string) => boolean | Promise<boolean>;
89
+ /** Unix milliseconds; defaults to now. */
90
+ now?: number;
91
+ }
92
+ /**
93
+ * Check a signed feed request — the half a policy SOURCE implements. Either
94
+ * header spelling (`forumulate-*` or `atlas-*`) is read.
95
+ */
96
+ export declare function verifyPolicyFeedRequest(input: VerifyPolicyFeedRequestInput): Promise<{
97
+ valid: true;
98
+ id: string;
99
+ } | {
100
+ valid: false;
101
+ reason: PolicyFeedRequestFailure;
102
+ }>;
103
+ export declare class PolicyWebhookError extends Error {
104
+ readonly reason: 'missing_headers' | 'stale_timestamp' | 'bad_signature' | 'bad_body';
105
+ constructor(reason: 'missing_headers' | 'stale_timestamp' | 'bad_signature' | 'bad_body', message: string);
106
+ }
107
+ export interface VerifyPolicyWebhookOptions {
108
+ /** Seconds a delivery may be old (or early). Default 300; 0 skips the check. */
109
+ toleranceSeconds?: number;
110
+ /** Unix milliseconds; defaults to now. */
111
+ now?: number;
112
+ }
113
+ /**
114
+ * Verify a `policy.published` / `policy.retired` notice and parse it. Throws
115
+ * {@link PolicyWebhookError} — the parse can never run ahead of the check.
116
+ *
117
+ * `body` must be the RAW request body: a re-serialised object has different
118
+ * bytes and never verifies. During a secret rotation pass both secrets.
119
+ * Dedupe on `webhook-id`: a retry of one event reuses it.
120
+ *
121
+ * ```ts
122
+ * const event = await verifyPolicyWebhook(req.headers, rawBody, process.env.POLICY_FEED_SECRET!);
123
+ * if (event.type === 'policy.published') await refreshPolicies(event.key);
124
+ * ```
125
+ */
126
+ export declare function verifyPolicyWebhook(headers: HeaderBag, body: string | Uint8Array, secret: string | readonly string[], opts?: VerifyPolicyWebhookOptions): Promise<PolicyWebhookEvent>;
127
+ /** Standard Webhooks headers for one notice — the SENDING half, for a platform implementing the protocol. */
128
+ export declare function signPolicyWebhook(input: {
129
+ secret: string | readonly string[];
130
+ body: string;
131
+ id?: string;
132
+ /** Unix seconds. Default: now. */
133
+ timestamp?: number;
134
+ }): Promise<{
135
+ 'webhook-id': string;
136
+ 'webhook-timestamp': string;
137
+ 'webhook-signature': string;
138
+ }>;
139
+ //# sourceMappingURL=policy-feed.d.ts.map