Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions src/worker/queues/send-webhook-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { getWebhooksByEventType } from "../../shared/utils/cache/get-webhook";
import { redis } from "../../shared/utils/redis/redis";
import { defaultJobOptions } from "./queues";
import { logger } from "../../shared/utils/logger";

export type EnqueueContractSubscriptionWebhookData = {
type: WebhooksEventTypes.CONTRACT_SUBSCRIPTION;
Expand Down Expand Up @@ -137,15 +138,41 @@ export class SendWebhookQueue {
...(await getWebhooksByEventType(data.type)),
];

logger({
service: "worker",
level: "info",
message: `[Webhook] Enqueuing transaction webhooks to queue for transaction ${data.queueId}`,
queueId: data.queueId,
data: {
eventType: data.type,
webhookCount: webhooks.length,
},
});

for (const webhook of webhooks) {
const job: WebhookJob = { data, webhook };
const serialized = SuperJSON.stringify(job);
const idempotencyKey = this._getTransactionWebhookIdempotencyKey({
webhook,
eventType: data.type,
queueId: data.queueId,
});

await this.q.add(`${data.type}:${webhook.id}`, serialized, {
jobId: this._getTransactionWebhookIdempotencyKey({
webhook,
jobId: idempotencyKey,
});

logger({
service: "worker",
level: "info",
message: `[Webhook] Transaction webhook added to queue for transaction ${data.queueId} at destination ${webhook.url}`,
queueId: data.queueId,
data: {
eventType: data.type,
queueId: data.queueId,
}),
destination: webhook.url,
webhookId: webhook.id,
idempotencyKey,
},
});
}
};
Expand Down
64 changes: 62 additions & 2 deletions src/worker/tasks/send-webhook-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ import { env } from "../../shared/utils/env";
const handler: Processor<string, void, string> = async (job: Job<string>) => {
const { data, webhook } = superjson.parse<WebhookJob>(job.data);

// Extract transaction ID if available
let transactionId: string | undefined;
if ("queueId" in data) {
transactionId = data.queueId;
}

// Log webhook attempt with HMAC info
const hmacMode = env.ENABLE_CUSTOM_HMAC_AUTH ? "custom" : "standard";
logger({
service: "worker",
level: "info",
message: `[Webhook] Attempting to send webhook for transaction ${transactionId} at destination ${webhook.url}`,
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
hmacMode,
},
});
Comment on lines +37 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle undefined transactionId in log messages.

For non-transaction webhook types (e.g., BACKEND_WALLET_BALANCE, WALLET_SUBSCRIPTION), transactionId will be undefined, resulting in log messages like "for transaction undefined". Consider using conditional formatting to improve log clarity.

Apply this diff to improve the log message:

-    message: `[Webhook] Attempting to send webhook for transaction ${transactionId} at destination ${webhook.url}`,
+    message: transactionId 
+      ? `[Webhook] Attempting to send webhook for transaction ${transactionId} at destination ${webhook.url}`
+      : `[Webhook] Attempting to send webhook at destination ${webhook.url}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Log webhook attempt with HMAC info
const hmacMode = env.ENABLE_CUSTOM_HMAC_AUTH ? "custom" : "standard";
logger({
service: "worker",
level: "info",
message: `[Webhook] Attempting to send webhook for transaction ${transactionId} at destination ${webhook.url}`,
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
hmacMode,
},
});
// Log webhook attempt with HMAC info
const hmacMode = env.ENABLE_CUSTOM_HMAC_AUTH ? "custom" : "standard";
logger({
service: "worker",
level: "info",
message: transactionId
? `[Webhook] Attempting to send webhook for transaction ${transactionId} at destination ${webhook.url}`
: `[Webhook] Attempting to send webhook at destination ${webhook.url}`,
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
hmacMode,
},
});
🤖 Prompt for AI Agents
In src/worker/tasks/send-webhook-worker.ts around lines 37 to 50, the log
includes "for transaction ${transactionId}" but transactionId can be undefined
for non-transaction webhook types; change the log to conditionally include the
transaction id only when present (e.g., "for transaction <id>" or otherwise use
the event type or a generic label like "non-transaction event"), and ensure
queueId/data fields reflect the same (omit queueId or set to a fallback such as
data.type) so logs are clear and don't show "undefined".


let resp: WebhookResponse | undefined;
switch (data.type) {
case WebhooksEventTypes.CONTRACT_SUBSCRIPTION: {
Expand Down Expand Up @@ -61,6 +82,17 @@ const handler: Processor<string, void, string> = async (job: Job<string>) => {
const transaction = await TransactionDB.get(data.queueId);
if (!transaction) {
job.log("Transaction not found.");
logger({
service: "worker",
level: "warn",
message: `[Webhook] Transaction not found for webhook`,
queueId: data.queueId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
},
});
return;
}
const webhookBody: Static<typeof TransactionSchema> =
Expand All @@ -85,16 +117,44 @@ const handler: Processor<string, void, string> = async (job: Job<string>) => {
}
}

// Log the response
if (resp) {
const logLevel = resp.ok ? "info" : resp.status >= 500 ? "error" : "warn";
logger({
service: "worker",
level: logLevel,
message: `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle undefined transactionId in log messages.

Similar to the earlier log, this message will show "for transaction undefined" for non-transaction webhooks.

Apply this diff:

-      message: `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`,
+      message: transactionId
+        ? `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`
+        : `[Webhook] Webhook response received: ${resp.status} at destination ${webhook.url}`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message: `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`,
message: transactionId
? `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`
: `[Webhook] Webhook response received: ${resp.status} at destination ${webhook.url}`,
🤖 Prompt for AI Agents
In src/worker/tasks/send-webhook-worker.ts around line 126, the log message
interpolates transactionId directly and can print "for transaction undefined"
for non-transaction webhooks; change the message construction to conditionally
include the "for transaction <id>" segment only when transactionId is defined
(or use a safe fallback like 'N/A') so logs don't show "undefined" — e.g. build
the message string with a ternary or template fragment that appends `for
transaction ${transactionId}` only when transactionId is truthy.

queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
responseCode: resp.status,
responseOk: resp.ok,
hmacMode,
responseBody: resp.body.substring(0, 200), // Truncate response body to first 200 chars
},
});
}
Comment on lines +120 to +138
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard against non-string response body.

Line 135 calls resp.body.substring(0, 200) without verifying that body is a string. If the webhook response body is an object, null, or undefined, this will throw a runtime error.

Apply this diff to safely handle the response body:

-        responseBody: resp.body.substring(0, 200), // Truncate response body to first 200 chars
+        responseBody: typeof resp.body === 'string' 
+          ? resp.body.substring(0, 200) 
+          : JSON.stringify(resp.body).substring(0, 200), // Truncate response body to first 200 chars
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Log the response
if (resp) {
const logLevel = resp.ok ? "info" : resp.status >= 500 ? "error" : "warn";
logger({
service: "worker",
level: logLevel,
message: `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`,
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
responseCode: resp.status,
responseOk: resp.ok,
hmacMode,
responseBody: resp.body.substring(0, 200), // Truncate response body to first 200 chars
},
});
}
// Log the response
if (resp) {
const logLevel = resp.ok ? "info" : resp.status >= 500 ? "error" : "warn";
logger({
service: "worker",
level: logLevel,
message: `[Webhook] Webhook response received: ${resp.status} for transaction ${transactionId} at destination ${webhook.url}`,
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
responseCode: resp.status,
responseOk: resp.ok,
hmacMode,
responseBody: typeof resp.body === 'string'
? resp.body.substring(0, 200)
: JSON.stringify(resp.body).substring(0, 200), // Truncate response body to first 200 chars
},
});
}
🤖 Prompt for AI Agents
In src/worker/tasks/send-webhook-worker.ts around lines 120-138, resp.body is
assumed to be a string and resp.body.substring(0, 200) can throw if body is
null/undefined or an object; update the logging to safely handle non-string
bodies by normalizing the body before truncation: if body is a string use it, if
it's an object stringify it (catching possible errors), and if null/undefined
use an empty string, then truncate the normalized string to 200 chars for
responseBody in the logger; ensure this normalization is done inline or via a
small helper and avoid calling substring on non-strings.


// Throw on 5xx so it remains in the queue to retry later.
if (resp && resp.status >= 500) {
const error = new Error(
`Received status ${resp.status} from webhook ${webhook.url}.`,
);
job.log(error.message);
logger({
level: "debug",
message: error.message,
level: "error",
message: `[Webhook] 5xx error, will retry`,
service: "worker",
queueId: transactionId,
data: {
eventType: data.type,
destination: webhook.url,
webhookId: webhook.id,
responseCode: resp.status,
hmacMode,
},
});
throw error;
}
Expand Down
Loading