import { timingSafeEqual } from 'crypto'; // Documenso (v1.13 + 2.x) authenticates outbound webhooks by sending the // configured secret in plaintext via the `X-Documenso-Secret` header. // There is no HMAC. Compare the provided value timing-safely to the env secret. // // An empty `expected` MUST always reject - without this guard, // timingSafeEqual(0-bytes, 0-bytes) returns true, so a dev environment // with a blank DOCUMENSO_WEBHOOK_SECRET would accept any request whose // `X-Documenso-Secret` was also empty/missing. Same for blank per-port // secret rows in `system_settings` (the per-port writer should never // store an empty string but defense-in-depth here is cheap). export function verifyDocumensoSecret(provided: string, expected: string): boolean { if (!provided || !expected) return false; if (provided.length !== expected.length) return false; try { return timingSafeEqual(Buffer.from(provided), Buffer.from(expected)); } catch { return false; } }