From 86c67bcf290673bf4237e7c047cc1afa81f60168 Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 23 May 2026 20:57:55 +0100 Subject: [PATCH] fix: support PascalCase properties in Ombi webhooks (#42) --- server/routes/webhook.js | 11 +++++++---- server/utils/ombiHelpers.js | 16 +++++++++------- tests/integration/webhook.test.js | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/server/routes/webhook.js b/server/routes/webhook.js index 68d5d23..bccc8f5 100644 --- a/server/routes/webhook.js +++ b/server/routes/webhook.js @@ -717,9 +717,12 @@ router.post('/ombi', webhookLimiter, (req, res) => { return res.status(401).json({ error: 'Unauthorized' }); } - // Ombi uses notificationType instead of eventType - const { notificationType, requestId, requestedUser, applicationUrl } = req.body; - const eventType = notificationType || req.body.eventType; + // Ombi uses notificationType instead of eventType. Support PascalCase for .NET apps + const notificationType = req.body.notificationType || req.body.NotificationType; + const requestId = req.body.requestId || req.body.RequestId; + const applicationUrl = req.body.applicationUrl || req.body.ApplicationUrl; + + const eventType = notificationType || req.body.eventType || req.body.EventType; // Extract username from requestedUser (handles both object and string formats) const username = extractRequestedUser(req.body); @@ -732,7 +735,7 @@ router.post('/ombi', webhookLimiter, (req, res) => { // Use applicationUrl as instance identifier for replay protection const instanceName = applicationUrl || 'ombi'; // Use requestId + eventType + current time as replay key - const eventDate = req.body.requestedDate || new Date().toISOString(); + const eventDate = req.body.requestedDate || req.body.RequestedDate || new Date().toISOString(); if (isReplay(eventType, instanceName, `${requestId}-${eventDate}`)) { logToFile(`[Webhook] Ombi duplicate event ignored: ${eventType} requestId=${requestId}`); diff --git a/server/utils/ombiHelpers.js b/server/utils/ombiHelpers.js index 864939a..42cff9c 100644 --- a/server/utils/ombiHelpers.js +++ b/server/utils/ombiHelpers.js @@ -15,17 +15,19 @@ function extractRequestedUser(request) { if (!request) return ''; + const requestedUser = request.requestedUser || request.RequestedUser; + // Handle object format: OmbiStore.Entities.OmbiUser - if (request.requestedUser && typeof request.requestedUser === 'object') { + if (requestedUser && typeof requestedUser === 'object') { // Priority: alias > userAlias > userName > normalizedUserName > requestedByAlias - return request.requestedUser.alias || - request.requestedUser.userAlias || - request.requestedUser.userName || - request.requestedUser.normalizedUserName || - request.requestedByAlias || ''; + return requestedUser.alias || requestedUser.Alias || + requestedUser.userAlias || requestedUser.UserAlias || + requestedUser.userName || requestedUser.UserName || + requestedUser.normalizedUserName || requestedUser.NormalizedUserName || + request.requestedByAlias || request.RequestedByAlias || ''; } // Handle string format (fallback for compatibility) - return request.requestedUser || request.requestedByAlias || ''; + return requestedUser || request.requestedByAlias || request.RequestedByAlias || ''; } function filterRequestsByUser(requests, username, showAll) { diff --git a/tests/integration/webhook.test.js b/tests/integration/webhook.test.js index 504b494..74bc154 100644 --- a/tests/integration/webhook.test.js +++ b/tests/integration/webhook.test.js @@ -647,5 +647,32 @@ describe('POST /api/webhook/ombi', () => { expect(res2.status).toBe(200); expect(res2.body.duplicate).toBe(true); }); + + it('returns 200 { received: true } for a valid NewRequest event with PascalCase payload', async () => { + const app = makeApp(); + + nock('https://ombi.test') + .get('/api/v1/Request/movie') + .reply(200, []); + nock('https://ombi.test') + .get('/api/v1/Request/tv') + .reply(200, []); + + const payload = { + NotificationType: 'NewRequest', + RequestId: 126, + RequestedUser: { UserName: 'gordon_pascal' }, + Title: 'Pascal Movie', + Type: 'Movie', + RequestStatus: 'Pending', + ApplicationUrl: 'https://ombi.test', + RequestedDate: '2026-05-23T20:33:00.000Z' + }; + + const res = await postOmbi(app, payload); + expect(res.status).toBe(200); + expect(res.body.received).toBe(true); + expect(res.body.duplicate).toBeUndefined(); + }); });