From aae172be414fb832b6036047235d797689e7192f Mon Sep 17 00:00:00 2001 From: Gabriel Miranda Date: Thu, 3 Sep 2026 09:32:19 -0300 Subject: [PATCH] feat(webhooks): add events.replay() --- package.json | 2 +- src/webhooks/events/events.ts | 16 ++++ src/webhooks/interfaces/index.ts | 5 ++ .../replay-webhook-event.interface.ts | 14 ++++ src/webhooks/webhooks.spec.ts | 75 +++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/webhooks/interfaces/replay-webhook-event.interface.ts diff --git a/package.json b/package.json index 1f068c50..c525eaba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "resend", - "version": "6.25.0", + "version": "6.26.0", "description": "Node.js library for the Resend API", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/src/webhooks/events/events.ts b/src/webhooks/events/events.ts index abea149e..6d9002da 100644 --- a/src/webhooks/events/events.ts +++ b/src/webhooks/events/events.ts @@ -10,6 +10,11 @@ import type { ListWebhookEventsResponse, ListWebhookEventsResponseSuccess, } from '../interfaces/list-webhook-events.interface'; +import type { + ReplayWebhookEventOptions, + ReplayWebhookEventResponse, + ReplayWebhookEventResponseSuccess, +} from '../interfaces/replay-webhook-event.interface'; import { Attempts } from './attempts/attempts'; export class Events { @@ -38,4 +43,15 @@ export class Events { ); return data; } + + async replay( + options: ReplayWebhookEventOptions, + ): Promise { + const { webhookId, eventId } = options; + + const data = await this.resend.post( + `/webhooks/${webhookId}/events/${eventId}/replay`, + ); + return data; + } } diff --git a/src/webhooks/interfaces/index.ts b/src/webhooks/interfaces/index.ts index 7e3a4d31..8e92956a 100644 --- a/src/webhooks/interfaces/index.ts +++ b/src/webhooks/interfaces/index.ts @@ -36,6 +36,11 @@ export type { RemoveWebhookResponse, RemoveWebhookResponseSuccess, } from './remove-webhook.interface'; +export type { + ReplayWebhookEventOptions, + ReplayWebhookEventResponse, + ReplayWebhookEventResponseSuccess, +} from './replay-webhook-event.interface'; export type { UpdateWebhookOptions, UpdateWebhookResponse, diff --git a/src/webhooks/interfaces/replay-webhook-event.interface.ts b/src/webhooks/interfaces/replay-webhook-event.interface.ts new file mode 100644 index 00000000..e0284b01 --- /dev/null +++ b/src/webhooks/interfaces/replay-webhook-event.interface.ts @@ -0,0 +1,14 @@ +import type { Response } from '../../interfaces'; + +export interface ReplayWebhookEventOptions { + webhookId: string; + eventId: string; +} + +export interface ReplayWebhookEventResponseSuccess { + object: 'webhook_event'; + id: string; +} + +export type ReplayWebhookEventResponse = + Response; diff --git a/src/webhooks/webhooks.spec.ts b/src/webhooks/webhooks.spec.ts index cf871150..7cd86d21 100644 --- a/src/webhooks/webhooks.spec.ts +++ b/src/webhooks/webhooks.spec.ts @@ -14,6 +14,7 @@ import type { ListWebhookEventAttemptsResponseSuccess } from './interfaces/list- import type { ListWebhookEventsResponseSuccess } from './interfaces/list-webhook-events.interface'; import type { ListWebhooksResponseSuccess } from './interfaces/list-webhooks.interface'; import type { RemoveWebhookResponseSuccess } from './interfaces/remove-webhook.interface'; +import type { ReplayWebhookEventResponseSuccess } from './interfaces/replay-webhook-event.interface'; import type { UpdateWebhookOptions, UpdateWebhookResponseSuccess, @@ -379,6 +380,80 @@ describe('Webhooks', () => { }); }); + describe('events.replay', () => { + const webhookId = '430eed87-632a-4ea6-90db-0aace67ec228'; + const eventId = 'msg_1srOrx2ZWZBpBUvZwXKQmoEYga2'; + + it('replays an event', async () => { + const response: ReplayWebhookEventResponseSuccess = { + object: 'webhook_event', + id: eventId, + }; + + fetchMock.mockOnce(JSON.stringify(response), { + status: 200, + headers: { + 'content-type': 'application/json', + }, + }); + + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop'); + + const result = await resend.webhooks.events.replay({ + webhookId, + eventId, + }); + expect(result).toEqual({ + data: response, + error: null, + headers: { + 'content-type': 'application/json', + }, + }); + + expect(fetchMock).toHaveBeenCalledWith( + `https://api.resend.com/webhooks/${webhookId}/events/${eventId}/replay`, + expect.objectContaining({ + method: 'POST', + headers: expect.any(Headers), + }), + ); + }); + + describe('when event not found', () => { + it('returns error', async () => { + const response: ErrorResponse = { + name: 'not_found', + message: 'Webhook event not found', + statusCode: 404, + }; + + fetchMock.mockOnce(JSON.stringify(response), { + status: 404, + headers: { + 'content-type': 'application/json', + }, + }); + + const resend = new Resend('re_zKa4RCko_Lhm9ost2YjNCctnPjbLw8Nop'); + + const result = resend.webhooks.events.replay({ webhookId, eventId }); + + await expect(result).resolves.toEqual({ + data: null, + error: { + message: 'Webhook event not found', + name: 'not_found', + statusCode: 404, + }, + headers: { + 'content-type': 'application/json', + }, + }); + }); + }); + }); + describe('events.attempts.list', () => { const webhookId = '430eed87-632a-4ea6-90db-0aace67ec228'; const eventId = 'msg_1srOrx2ZWZBpBUvZwXKQmoEYga2';