Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ const endpoint = await relay.outbound.endpoints.create("customer_42", {
eventTypes: ["invoice.paid"],
});
// endpoint.secret is the signing secret your customer verifies deliveries with.
// Migrating from Svix or another Standard Webhooks sender? Pass `secret` with the
// endpoint's existing whsec_ secret so your customer keeps verifying with it.

// Every time the event happens.
const message = await relay.outbound.messages.publish(
Expand Down
6 changes: 4 additions & 2 deletions src/resources/outbound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
OutboundConsumer,
OutboundDelivery,
OutboundEndpoint,
OutboundEndpointCreateParams,
OutboundEndpointParams,
OutboundEndpointWithSecret,
OutboundEventType,
Expand Down Expand Up @@ -140,7 +141,8 @@ export class OutboundEndpointsResource {

/**
* Register an HTTPS endpoint for a consumer. The result carries the signing
* secret your customer verifies deliveries with.
* secret your customer verifies deliveries with: a new one, or `secret` when
* importing an existing Standard Webhooks secret (e.g. from Svix).
*
* ```ts
* const endpoint = await relay.outbound.endpoints.create("customer_42", {
Expand All @@ -149,7 +151,7 @@ export class OutboundEndpointsResource {
* });
* ```
*/
create(consumerId: string, params: OutboundEndpointParams): Promise<OutboundEndpointWithSecret> {
create(consumerId: string, params: OutboundEndpointCreateParams): Promise<OutboundEndpointWithSecret> {
return this.http.unwrap(
this.http.api.v1.outboundConsumersEndpointsCreate(
segment(consumerId),
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,16 @@ export interface OutboundEndpointParams {
functionId?: string;
}

/** Endpoint configuration for `endpoints.create`, with an optional secret to import. */
export interface OutboundEndpointCreateParams extends OutboundEndpointParams {
/**
* Existing Standard Webhooks signing secret to import (`whsec_` + base64 of a
* 24-64 byte key, e.g. exported from Svix) so the consumer keeps verifying
* with the key it already has. Omit it to generate a new secret.
*/
secret?: string;
}

export interface PublishOutboundMessageParams {
consumer: string;
event_type?: string;
Expand Down
14 changes: 14 additions & 0 deletions test/outbound.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ describe("outbound", () => {
expect(calls[1]?.body).toEqual({ url: "https://example.com/v2", event_types: ["a"] });
});

it("imports an existing signing secret on create", async () => {
const secret = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
const { relay, calls } = createClient(() => ({ json: { id: "endpoint-1", secret } }));

const endpoint = await relay.outbound.endpoints.create("customer_42", {
url: "https://example.com/hook",
eventTypes: ["invoice.paid"],
secret,
});

expect(endpoint.secret).toBe(secret);
expect(calls[0]?.body).toEqual({ url: "https://example.com/hook", event_types: ["invoice.paid"], secret });
});

it("pauses, resumes and handles signing secrets", async () => {
const { relay, calls } = createClient((url) =>
url.includes("/secret/") ? { json: { secret: "whsec_new" } } : { json: { id: "endpoint-1", state: "paused" } },
Expand Down
Loading