| 49 | * JSON object, 503 when no signing secret is configured. |
| 50 | */ |
| 51 | export const makeWorkOsWebhookRoute = (deps: WorkOsWebhookDeps) => |
| 52 | HttpRouter.add( |
| 53 | "POST", |
| 54 | WORKOS_WEBHOOK_PATH, |
| 55 | Effect.gen(function* () { |
| 56 | if (deps.secret === undefined) { |
| 57 | yield* Effect.logError( |
| 58 | "workos_webhook: WORKOS_WEBHOOK_SECRET is not set; refusing the delivery", |
| 59 | ); |
| 60 | return HttpServerResponse.empty({ status: 503 }); |
| 61 | } |
| 62 | const secret = deps.secret; |
| 63 | const request = yield* HttpServerRequest.HttpServerRequest; |
| 64 | const sigHeader = Headers.get(request.headers, SIGNATURE_HEADER); |
| 65 | if (Option.isNone(sigHeader)) { |
| 66 | return HttpServerResponse.empty({ status: 400 }); |
| 67 | } |
| 68 | const body = yield* request.json.pipe(Effect.option); |
| 69 | const payload = Option.flatMap(body, decodeWebhookPayload); |
| 70 | if (Option.isNone(payload)) { |
| 71 | return HttpServerResponse.empty({ status: 400 }); |
| 72 | } |
| 73 | |
| 74 | const workos = yield* WorkOSClient; |
| 75 | const verified = yield* workos |
| 76 | .constructWebhookEvent({ |
| 77 | payload: payload.value, |
| 78 | sigHeader: sigHeader.value, |
| 79 | secret, |
| 80 | }) |
| 81 | .pipe(Effect.option); |
| 82 | if (Option.isNone(verified)) { |
| 83 | yield* Effect.logWarning("workos_webhook: signature rejected"); |
| 84 | return HttpServerResponse.empty({ status: 400 }); |
| 85 | } |
| 86 | |
| 87 | yield* Effect.logInfo("workos_webhook: verified delivery; poking the reconciler", { |
| 88 | event: verified.value.event, |
| 89 | eventId: verified.value.id, |
| 90 | }); |
| 91 | deps.detach(deps.sync()); |
| 92 | return HttpServerResponse.empty({ status: 200 }); |
| 93 | }), |
| 94 | ); |