| 108 | } |
| 109 | |
| 110 | function buildHeaders(input: { |
| 111 | config: WebhookDestinationConfig |
| 112 | credentials: WebhookDestinationCredentials |
| 113 | body: Buffer |
| 114 | contentType: string |
| 115 | metadata?: DeliveryMetadata |
| 116 | isProbe?: boolean |
| 117 | }): Record<string, string> { |
| 118 | const timestamp = Math.floor(Date.now() / 1000) |
| 119 | const headers: Record<string, string> = { |
| 120 | 'Content-Type': input.contentType, |
| 121 | 'User-Agent': USER_AGENT, |
| 122 | 'X-Sim-Timestamp': timestamp.toString(), |
| 123 | 'X-Sim-Signature-Version': SIGNATURE_VERSION, |
| 124 | [input.config.signatureHeader ?? 'X-Sim-Signature']: sign( |
| 125 | input.body, |
| 126 | input.credentials.signingSecret, |
| 127 | timestamp |
| 128 | ), |
| 129 | } |
| 130 | if (input.metadata) { |
| 131 | headers['X-Sim-Drain-Id'] = input.metadata.drainId |
| 132 | headers['X-Sim-Run-Id'] = input.metadata.runId |
| 133 | headers['X-Sim-Source'] = input.metadata.source |
| 134 | headers['X-Sim-Sequence'] = input.metadata.sequence.toString() |
| 135 | headers['X-Sim-Row-Count'] = input.metadata.rowCount.toString() |
| 136 | // Stable across retries of the same chunk so receivers can dedupe. |
| 137 | headers['Idempotency-Key'] = `${input.metadata.runId}-${input.metadata.sequence}` |
| 138 | } |
| 139 | if (input.isProbe) { |
| 140 | headers['X-Sim-Probe'] = '1' |
| 141 | } |
| 142 | if (input.credentials.bearerToken) { |
| 143 | headers.Authorization = `Bearer ${input.credentials.bearerToken}` |
| 144 | } |
| 145 | return headers |
| 146 | } |
| 147 | |
| 148 | export const webhookDestination: DrainDestination< |
| 149 | WebhookDestinationConfig, |