I have a express.js application written in TypeScript and am getting a weird issue I have never seen before, some detail:
Whilst the request is being processed by the route – /api/webhook/sellix/<shopName>
– the request changes the path – it goes to /sellix/<shopName>
– and calls the catch route I have added for invalid routes which is using app.use
, then returns a 404 error, however the main route is still processing the request and finishes processing it even after the wrong response has been sent.
This is my route code:
<code>app.post('/api/webhook/sellix/:shopName', async (req, res) => {
if (!req.webhook_token) { // Check if the user is authenticated
return res.api.error('You must be authenticated to use this API.', 401);
if (!req.webhook_token?.routes.includes('sellix')) { // Check if the authentication token has access to this route
return res.api.error('You do not have permission to use this API.', 403)
if (!req.body || !req.body.data) {
return res.api.error('Invalid request', 400);
const shopId = req.body.data.shop_id;
const headerSig = req.headers['x-sellix-unescaped-signature'];
const secretKey = getEnv(`SELLIX_SECRET_${shopId}`); // Get the secret key from the environment
return res.api.error('Invalid shop ID', 400);
const hmac = createHmac('sha512', secretKey.string!);
hmac.update(JSON.stringify(req.body));
const sig = hmac.digest('hex');
if (timingSafeEqual(Buffer.from(sig), Buffer.from(headerSig as string)) === false) {
return res.api.error('Invalid signature', 401);
const event = req.body.event;
const discordId = req.body.data.custom_fields.discord_id.toString();
const discordUsername = req.body.data.custom_fields.discord_user.toString().replace('#0', '');
const shopName = req.params.shopName;
if (!event || !shopId || !discordId || !discordUsername) {
return res.api.error('Invalid request', 400);
if (!validUserId(discordId) || !validUsername(discordUsername)) {
return res.api.error('Invalid user data', 400);
let endTimestamp: number;
if (event.startsWith('subscription')) {
let endPeriod = req.body.data.current_period_end;
if (!endPeriod || isNaN(endPeriod)) {
return res.api.error('Invalid subscription period', 400);
endTimestamp = endPeriod;
if (shopId == 0 && shopName === '...') {
res.debug(`Updating subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3); // Extended express debug function
if (event == 'subscription:created') {
sendNotification('high', 'sellix_events', `Subscription created for ${discordUsername}`, 'Sellix API Event', 'sunglasses').catch(() => { }); // Sends a notification using ntfy
} else if (event == 'subscription:cancelled') {
sendNotification('high', 'sellix_events', `Subscription cancelled for ${discordUsername}`, 'Sellix API Event', 'hankey').catch(() => { });
// req.path is still correct
await setSubscriptionExpiry(discordId, endTimestamp!); // Update the subscription status in the database
// req.path is wrong and another route has been called
res.debug(`Updated subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3);
return res.api.success({ message: 'Event processed' }); // This response doesn't get sent
console.error(`Failed to update subscription expiry for ${discordUsername}, end period: ${endTimestamp!}nnFailed with error: ${err}`);
sendNotification('urgent', 'sellix_errors', `Failed to update subscription expiry for ${discordUsername}nnFailed with error: ${err}`, 'Sellix API Error', 'warning').catch(() => { });
return res.api.error('Failed to update subscription expiry', 500);
res.api.error('Not implemented', 400);
<code>app.post('/api/webhook/sellix/:shopName', async (req, res) => {
if (!req.webhook_token) { // Check if the user is authenticated
return res.api.error('You must be authenticated to use this API.', 401);
}
if (!req.webhook_token?.routes.includes('sellix')) { // Check if the authentication token has access to this route
return res.api.error('You do not have permission to use this API.', 403)
}
if (!req.body || !req.body.data) {
return res.api.error('Invalid request', 400);
}
const shopId = req.body.data.shop_id;
const headerSig = req.headers['x-sellix-unescaped-signature'];
const secretKey = getEnv(`SELLIX_SECRET_${shopId}`); // Get the secret key from the environment
if (!secretKey.isset) {
return res.api.error('Invalid shop ID', 400);
}
const hmac = createHmac('sha512', secretKey.string!);
hmac.update(JSON.stringify(req.body));
const sig = hmac.digest('hex');
if (timingSafeEqual(Buffer.from(sig), Buffer.from(headerSig as string)) === false) {
return res.api.error('Invalid signature', 401);
}
const event = req.body.event;
const discordId = req.body.data.custom_fields.discord_id.toString();
const discordUsername = req.body.data.custom_fields.discord_user.toString().replace('#0', '');
const shopName = req.params.shopName;
if (!event || !shopId || !discordId || !discordUsername) {
return res.api.error('Invalid request', 400);
}
if (!validUserId(discordId) || !validUsername(discordUsername)) {
return res.api.error('Invalid user data', 400);
}
let endTimestamp: number;
if (event.startsWith('subscription')) {
let endPeriod = req.body.data.current_period_end;
if (!endPeriod || isNaN(endPeriod)) {
return res.api.error('Invalid subscription period', 400);
}
endTimestamp = endPeriod;
}
if (shopId == 0 && shopName === '...') {
try {
res.debug(`Updating subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3); // Extended express debug function
if (event == 'subscription:created') {
sendNotification('high', 'sellix_events', `Subscription created for ${discordUsername}`, 'Sellix API Event', 'sunglasses').catch(() => { }); // Sends a notification using ntfy
} else if (event == 'subscription:cancelled') {
sendNotification('high', 'sellix_events', `Subscription cancelled for ${discordUsername}`, 'Sellix API Event', 'hankey').catch(() => { });
}
// req.path is still correct
await setSubscriptionExpiry(discordId, endTimestamp!); // Update the subscription status in the database
// req.path is wrong and another route has been called
res.debug(`Updated subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3);
return res.api.success({ message: 'Event processed' }); // This response doesn't get sent
} catch (err) {
console.error(`Failed to update subscription expiry for ${discordUsername}, end period: ${endTimestamp!}nnFailed with error: ${err}`);
sendNotification('urgent', 'sellix_errors', `Failed to update subscription expiry for ${discordUsername}nnFailed with error: ${err}`, 'Sellix API Error', 'warning').catch(() => { });
return res.api.error('Failed to update subscription expiry', 500);
}
}
res.api.error('Not implemented', 400);
});
</code>
app.post('/api/webhook/sellix/:shopName', async (req, res) => {
if (!req.webhook_token) { // Check if the user is authenticated
return res.api.error('You must be authenticated to use this API.', 401);
}
if (!req.webhook_token?.routes.includes('sellix')) { // Check if the authentication token has access to this route
return res.api.error('You do not have permission to use this API.', 403)
}
if (!req.body || !req.body.data) {
return res.api.error('Invalid request', 400);
}
const shopId = req.body.data.shop_id;
const headerSig = req.headers['x-sellix-unescaped-signature'];
const secretKey = getEnv(`SELLIX_SECRET_${shopId}`); // Get the secret key from the environment
if (!secretKey.isset) {
return res.api.error('Invalid shop ID', 400);
}
const hmac = createHmac('sha512', secretKey.string!);
hmac.update(JSON.stringify(req.body));
const sig = hmac.digest('hex');
if (timingSafeEqual(Buffer.from(sig), Buffer.from(headerSig as string)) === false) {
return res.api.error('Invalid signature', 401);
}
const event = req.body.event;
const discordId = req.body.data.custom_fields.discord_id.toString();
const discordUsername = req.body.data.custom_fields.discord_user.toString().replace('#0', '');
const shopName = req.params.shopName;
if (!event || !shopId || !discordId || !discordUsername) {
return res.api.error('Invalid request', 400);
}
if (!validUserId(discordId) || !validUsername(discordUsername)) {
return res.api.error('Invalid user data', 400);
}
let endTimestamp: number;
if (event.startsWith('subscription')) {
let endPeriod = req.body.data.current_period_end;
if (!endPeriod || isNaN(endPeriod)) {
return res.api.error('Invalid subscription period', 400);
}
endTimestamp = endPeriod;
}
if (shopId == 0 && shopName === '...') {
try {
res.debug(`Updating subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3); // Extended express debug function
if (event == 'subscription:created') {
sendNotification('high', 'sellix_events', `Subscription created for ${discordUsername}`, 'Sellix API Event', 'sunglasses').catch(() => { }); // Sends a notification using ntfy
} else if (event == 'subscription:cancelled') {
sendNotification('high', 'sellix_events', `Subscription cancelled for ${discordUsername}`, 'Sellix API Event', 'hankey').catch(() => { });
}
// req.path is still correct
await setSubscriptionExpiry(discordId, endTimestamp!); // Update the subscription status in the database
// req.path is wrong and another route has been called
res.debug(`Updated subscription expiry for ${discordUsername}, end period: ${endTimestamp!}`)(3);
return res.api.success({ message: 'Event processed' }); // This response doesn't get sent
} catch (err) {
console.error(`Failed to update subscription expiry for ${discordUsername}, end period: ${endTimestamp!}nnFailed with error: ${err}`);
sendNotification('urgent', 'sellix_errors', `Failed to update subscription expiry for ${discordUsername}nnFailed with error: ${err}`, 'Sellix API Error', 'warning').catch(() => { });
return res.api.error('Failed to update subscription expiry', 500);
}
}
res.api.error('Not implemented', 400);
});
This is my database function for updating the expiry:
<code>function setSubscriptionExpiry(discordId: string, expiry: number): Promise<null> {
return new Promise((resolve, reject) => {
'INSERT INTO `users` (`userid`, `subscription_expires`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `subscription_expires` = ?',
[discordId, expiry, expiry],
reject(`Failed to set subscription expiry: ${err}`);
<code>function setSubscriptionExpiry(discordId: string, expiry: number): Promise<null> {
return new Promise((resolve, reject) => {
pool.query(
'INSERT INTO `users` (`userid`, `subscription_expires`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `subscription_expires` = ?',
[discordId, expiry, expiry],
(err) => {
if (err) {
reject(`Failed to set subscription expiry: ${err}`);
return;
}
resolve(null);
}
);
});
}
</code>
function setSubscriptionExpiry(discordId: string, expiry: number): Promise<null> {
return new Promise((resolve, reject) => {
pool.query(
'INSERT INTO `users` (`userid`, `subscription_expires`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `subscription_expires` = ?',
[discordId, expiry, expiry],
(err) => {
if (err) {
reject(`Failed to set subscription expiry: ${err}`);
return;
}
resolve(null);
}
);
});
}
As you can see I have a few debug lines this is the output:
<code>[1] [DEBUG-3] [127.0.0.1] [POST] /api/webhook/sellix/... Updating subscription expiry for ..., end period: 1716769402
[1] [DEBUG-2] [127.0.0.1] [POST] /sellix/... Not Found
[1] [DEBUG-3] [127.0.0.1] [POST] /sellix/... Updated subscription expiry for ..., end period: 1716769402
<code>[1] [DEBUG-3] [127.0.0.1] [POST] /api/webhook/sellix/... Updating subscription expiry for ..., end period: 1716769402
[1] [DEBUG-2] [127.0.0.1] [POST] /sellix/... Not Found
[1] [DEBUG-3] [127.0.0.1] [POST] /sellix/... Updated subscription expiry for ..., end period: 1716769402
</code>
[1] [DEBUG-3] [127.0.0.1] [POST] /api/webhook/sellix/... Updating subscription expiry for ..., end period: 1716769402
[1] [DEBUG-2] [127.0.0.1] [POST] /sellix/... Not Found
[1] [DEBUG-3] [127.0.0.1] [POST] /sellix/... Updated subscription expiry for ..., end period: 1716769402
This is the route which is catching it and returning the 404:
<code>app.use('/api', (req, res) => {
if (app._router.stack.includes(req.path)) {
res.api.error('Method Not Allowed', 405);
res.debug('Not Found')(2);
res.api.error('Not Found', 404);
<code>app.use('/api', (req, res) => {
if (app._router.stack.includes(req.path)) {
res.api.error('Method Not Allowed', 405);
} else {
res.debug('Not Found')(2);
res.api.error('Not Found', 404);
}
});
</code>
app.use('/api', (req, res) => {
if (app._router.stack.includes(req.path)) {
res.api.error('Method Not Allowed', 405);
} else {
res.debug('Not Found')(2);
res.api.error('Not Found', 404);
}
});