Skip to content

Commit 6239b3a

Browse files
committed
feat: Event Verify Type Signature, Aggressively Push Status Checks in Webhook
1 parent 1a72edb commit 6239b3a

File tree

4 files changed

+37
-15
lines changed

4 files changed

+37
-15
lines changed

examples/webhook.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { env } from './utils';
1212
env();
1313

1414
const PORT = 3000;
15-
const WAIT_FOR_TASK_FINISH_TIMEOUT = 60_000;
15+
const WAIT_FOR_TASK_FINISH_TIMEOUT = 3 * 60_000;
1616

1717
// Environment ---------------------------------------------------------------
1818

@@ -57,7 +57,7 @@ async function main() {
5757

5858
const event = await verifyWebhookEventSignature(
5959
{
60-
evt: body,
60+
body,
6161
signature,
6262
timestamp,
6363
},

src/lib/bin/commands/listen.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,20 @@ export const listen = new Command('listen')
115115
}
116116
}
117117

118-
if (queue.current.length === 0) {
119-
return;
120-
}
121-
122-
const promises = queue.current.map(async (update) => {
118+
// Send Events
119+
120+
const events: (Webhook & { internal?: true })[] = [
121+
// NOTE: We push the ping request on every tick to ensure the webhook is alive.
122+
{
123+
type: 'test',
124+
timestamp: new Date().toISOString(),
125+
payload: { test: 'ok' },
126+
internal: true,
127+
},
128+
...queue.current,
129+
];
130+
131+
const promises = events.map(async (update) => {
123132
const body = JSON.stringify(update);
124133

125134
const signature = createWebhookSignature({
@@ -155,7 +164,9 @@ export const listen = new Command('listen')
155164
const delivery = await Promise.all(promises);
156165

157166
// NOTE: We preserve the rejected updates so we can retry them.
158-
queue.current = delivery.filter((d) => d.delivery === 'rejected').map((d) => d.update);
167+
queue.current = delivery
168+
.filter((d) => d.delivery === 'rejected' && d.update.internal !== true)
169+
.map((d) => d.update);
159170
}, 1_000);
160171

161172
console.log(`Forwarding updates to: ${localTargetEndpoint}!`);

src/lib/webhooks.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type WebhookTestPayload = z.infer<typeof zWebhookTestPayload>;
1818

1919
export const zWebhookTest = z.object({
2020
type: z.literal('test'),
21-
timestamp: z.iso.datetime({ offset: true }),
21+
timestamp: zWebhookTimestamp,
2222
payload: zWebhookTestPayload,
2323
});
2424

@@ -66,14 +66,15 @@ export type Webhook = z.infer<typeof zWebhookSchema>;
6666
*/
6767
export async function verifyWebhookEventSignature(
6868
evt: {
69-
evt: string;
69+
body: string | object;
7070
signature: string;
7171
timestamp: string;
7272
},
7373
cfg: { secret: string },
7474
): Promise<{ ok: true; event: Webhook } | { ok: false }> {
7575
try {
76-
const event = await zWebhookSchema.safeParseAsync(JSON.parse(evt.evt));
76+
const json = typeof evt.body === 'string' ? JSON.parse(evt.body) : evt.body;
77+
const event = await zWebhookSchema.safeParseAsync(json);
7778

7879
if (event.success === false) {
7980
return { ok: false };

tests/lib/webhooks.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,18 @@ describe('webhooks', () => {
7979
timestamp,
8080
});
8181

82-
const valid = await verifyWebhookEventSignature(
82+
const validJSON = await verifyWebhookEventSignature(
8383
{
84-
evt: JSON.stringify(MOCK),
84+
body: MOCK,
85+
signature: signature,
86+
timestamp,
87+
},
88+
{ secret: 'secret' },
89+
);
90+
91+
const validString = await verifyWebhookEventSignature(
92+
{
93+
body: JSON.stringify(MOCK),
8594
signature: signature,
8695
timestamp,
8796
},
@@ -90,14 +99,15 @@ describe('webhooks', () => {
9099

91100
const invalid = await verifyWebhookEventSignature(
92101
{
93-
evt: JSON.stringify(MOCK),
102+
body: JSON.stringify(MOCK),
94103
signature: 'invalid',
95104
timestamp,
96105
},
97106
{ secret: 'secret' },
98107
);
99108

100-
expect(valid.ok).toBe(true);
109+
expect(validJSON.ok).toBe(true);
110+
expect(validString.ok).toBe(true);
101111
expect(invalid.ok).toBe(false);
102112
});
103113
});

0 commit comments

Comments
 (0)