(
path: string,
req: { body: unknown; headers: Record<string, string> },
)
| 284 | // Public inbound webhook receiver (no auth) |
| 285 | |
| 286 | async receiveWebhook( |
| 287 | path: string, |
| 288 | req: { body: unknown; headers: Record<string, string> }, |
| 289 | ): Promise<{ status: string; runId?: string }> { |
| 290 | // Look up webhook by path |
| 291 | const webhook = await this.repository.findByPath(path); |
| 292 | if (!webhook) { |
| 293 | this.logger.warn(`Webhook path not found: ${path}`); |
| 294 | throw new NotFoundException('Webhook not found'); |
| 295 | } |
| 296 | |
| 297 | if (webhook.status !== 'active') { |
| 298 | this.logger.warn(`Webhook ${webhook.id} is not active`); |
| 299 | throw new BadRequestException('Webhook is not active'); |
| 300 | } |
| 301 | |
| 302 | // Create delivery record |
| 303 | const delivery = await this.deliveryRepository.create({ |
| 304 | webhookId: webhook.id, |
| 305 | workflowRunId: null, |
| 306 | status: 'processing', |
| 307 | payload: typeof req.body === 'object' ? (req.body as any) : {}, |
| 308 | headers: req.headers, |
| 309 | parsedData: null, |
| 310 | errorMessage: null, |
| 311 | createdAt: new Date(), |
| 312 | completedAt: null, |
| 313 | }); |
| 314 | |
| 315 | this.logger.log(`Received webhook ${webhook.id}, delivery ${delivery.id}`); |
| 316 | |
| 317 | try { |
| 318 | // Execute parsing script |
| 319 | const parsedData = await this.executeParsingScript( |
| 320 | webhook.parsingScript, |
| 321 | typeof req.body === 'object' ? (req.body as any) : {}, |
| 322 | req.headers, |
| 323 | ); |
| 324 | |
| 325 | // Validate parsed data against expected inputs |
| 326 | const validationErrors = this.validateParsedData(webhook.expectedInputs as any, parsedData); |
| 327 | if (validationErrors.length > 0) { |
| 328 | throw new BadRequestException( |
| 329 | `Parsed data validation failed: ${validationErrors.map((e) => e.message).join(', ')}`, |
| 330 | ); |
| 331 | } |
| 332 | |
| 333 | // Trigger workflow with organization context from webhook |
| 334 | const triggerAuth: AuthContext = { |
| 335 | userId: 'webhook-trigger', |
| 336 | organizationId: webhook.organizationId, |
| 337 | roles: ['MEMBER'], |
| 338 | isAuthenticated: true, |
| 339 | provider: 'internal', |
| 340 | }; |
| 341 | |
| 342 | const prepared = await this.workflowsService.prepareRunPayload( |
| 343 | webhook.workflowId, |
no test coverage detected