Skip to content
Closed
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
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ WEBHOOK_EVENTS_TYPEBOT_CHANGE_STATUS=false
WEBHOOK_EVENTS_ERRORS=false
WEBHOOK_EVENTS_ERRORS_WEBHOOK=

# Webhook timeout and retry configuration
WEBHOOK_REQUEST_TIMEOUT_MS=60000
WEBHOOK_RETRY_MAX_ATTEMPTS=10
WEBHOOK_RETRY_INITIAL_DELAY_SECONDS=5
WEBHOOK_RETRY_USE_EXPONENTIAL_BACKOFF=true
WEBHOOK_RETRY_MAX_DELAY_SECONDS=300
WEBHOOK_RETRY_JITTER_FACTOR=0.2
# Comma separated list of HTTP status codes that should not trigger retries
WEBHOOK_RETRY_NON_RETRYABLE_STATUS_CODES=400,401,403,404,422

# Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_CLIENT=Evolution API
# Browser Name = Chrome | Firefox | Edge | Opera | Safari
Expand Down
55 changes: 48 additions & 7 deletions src/api/integrations/event/webhook/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
const httpService = axios.create({
baseURL,
headers: webhookHeaders as Record<string, string> | undefined,
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
});

await this.retryWebhookRequest(httpService, webhookData, `${origin}.sendData-Webhook`, baseURL, serverUrl);
Expand Down Expand Up @@ -156,7 +157,10 @@

try {
if (regex.test(globalURL)) {
const httpService = axios.create({ baseURL: globalURL });
const httpService = axios.create({
baseURL: globalURL,
timeout: webhookConfig.REQUEST?.TIMEOUT_MS ?? 30000,
});
Comment on lines +160 to +163

Choose a reason for hiding this comment

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

Acredito que de para criar um método que receba a url e o timeout em milisegundos como parâmetro pra poder usar tanto nessas linhas quanto na linha 115-118


await this.retryWebhookRequest(
httpService,
Expand Down Expand Up @@ -190,12 +194,20 @@
origin: string,
baseURL: string,
serverUrl: string,
maxRetries = 10,
delaySeconds = 30,
maxRetries?: number,
delaySeconds?: number,
): Promise<void> {
const webhookConfig = configService.get<Webhook>('WEBHOOK');
const maxRetryAttempts = maxRetries ?? webhookConfig.RETRY?.MAX_ATTEMPTS ?? 10;
const initialDelay = delaySeconds ?? webhookConfig.RETRY?.INITIAL_DELAY_SECONDS ?? 5;
const useExponentialBackoff = webhookConfig.RETRY?.USE_EXPONENTIAL_BACKOFF ?? true;
const maxDelay = webhookConfig.RETRY?.MAX_DELAY_SECONDS ?? 300;
const jitterFactor = webhookConfig.RETRY?.JITTER_FACTOR ?? 0.2;
const nonRetryableStatusCodes = webhookConfig.RETRY?.NON_RETRYABLE_STATUS_CODES ?? [400, 401, 403, 404, 422];

let attempts = 0;

while (attempts < maxRetries) {
while (attempts < maxRetryAttempts) {
try {
await httpService.post('', webhookData);
if (attempts > 0) {
Expand All @@ -208,25 +220,54 @@
return;
} catch (error) {
attempts++;

Check failure on line 223 in src/api/integrations/event/webhook/webhook.controller.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Delete `········`
const isTimeout = error.code === 'ECONNABORTED';

Check failure on line 225 in src/api/integrations/event/webhook/webhook.controller.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Delete `········`
if (error?.response?.status && nonRetryableStatusCodes.includes(error.response.status)) {
this.logger.error({
local: `${origin}`,
message: `Erro não recuperável (${error.response.status}): ${error?.message}. Cancelando retentativas.`,
statusCode: error?.response?.status,
url: baseURL,
server_url: serverUrl,
});
throw error;
}

this.logger.error({
local: `${origin}`,
message: `Tentativa ${attempts}/${maxRetries} falhou: ${error?.message}`,
message: `Tentativa ${attempts}/${maxRetryAttempts} falhou: ${isTimeout ? 'Timeout da requisição' : error?.message}`,
hostName: error?.hostname,
syscall: error?.syscall,
code: error?.code,
isTimeout,
statusCode: error?.response?.status,
error: error?.errno,
stack: error?.stack,
name: error?.name,
url: baseURL,
server_url: serverUrl,
});

if (attempts === maxRetries) {
if (attempts === maxRetryAttempts) {
throw error;
}

await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1000));
let nextDelay = initialDelay;
if (useExponentialBackoff) {
nextDelay = Math.min(initialDelay * Math.pow(2, attempts - 1), maxDelay);

Check failure on line 259 in src/api/integrations/event/webhook/webhook.controller.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Delete `··········`
const jitter = nextDelay * jitterFactor * (Math.random() * 2 - 1);
nextDelay = Math.max(initialDelay, nextDelay + jitter);
}

this.logger.log({
local: `${origin}`,
message: `Aguardando ${nextDelay.toFixed(1)} segundos antes da próxima tentativa`,
url: baseURL,
});

await new Promise((resolve) => setTimeout(resolve, nextDelay * 1000));
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion src/config/env.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,21 @@
TTL: number;
};
export type SslConf = { PRIVKEY: string; FULLCHAIN: string };
export type Webhook = { GLOBAL?: GlobalWebhook; EVENTS: EventsWebhook };
export type Webhook = {

Check failure on line 232 in src/config/env.config.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Delete `·`
GLOBAL?: GlobalWebhook;

Check failure on line 233 in src/config/env.config.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Delete `·`
EVENTS: EventsWebhook;
REQUEST?: {
TIMEOUT_MS?: number;
};
RETRY?: {
MAX_ATTEMPTS?: number;
INITIAL_DELAY_SECONDS?: number;
USE_EXPONENTIAL_BACKOFF?: boolean;
MAX_DELAY_SECONDS?: number;
JITTER_FACTOR?: number;
NON_RETRYABLE_STATUS_CODES?: number[];
};
};
export type Pusher = { ENABLED: boolean; GLOBAL?: GlobalPusher; EVENTS: EventsPusher };
export type ConfigSessionPhone = { CLIENT: string; NAME: string; VERSION: string };
export type QrCode = { LIMIT: number; COLOR: string };
Expand Down Expand Up @@ -543,6 +557,17 @@
ERRORS: process.env?.WEBHOOK_EVENTS_ERRORS === 'true',
ERRORS_WEBHOOK: process.env?.WEBHOOK_EVENTS_ERRORS_WEBHOOK || '',
},
REQUEST: {
TIMEOUT_MS: Number.parseInt(process.env?.WEBHOOK_REQUEST_TIMEOUT_MS) || 30000,
},
RETRY: {
MAX_ATTEMPTS: Number.parseInt(process.env?.WEBHOOK_RETRY_MAX_ATTEMPTS) || 10,
INITIAL_DELAY_SECONDS: Number.parseInt(process.env?.WEBHOOK_RETRY_INITIAL_DELAY_SECONDS) || 5,
USE_EXPONENTIAL_BACKOFF: process.env?.WEBHOOK_RETRY_USE_EXPONENTIAL_BACKOFF !== 'false',
MAX_DELAY_SECONDS: Number.parseInt(process.env?.WEBHOOK_RETRY_MAX_DELAY_SECONDS) || 300,
JITTER_FACTOR: Number.parseFloat(process.env?.WEBHOOK_RETRY_JITTER_FACTOR) || 0.2,
NON_RETRYABLE_STATUS_CODES: process.env?.WEBHOOK_RETRY_NON_RETRYABLE_STATUS_CODES?.split(',').map(Number) || [400, 401, 403, 404, 422],

Check failure on line 569 in src/config/env.config.ts

View workflow job for this annotation

GitHub Actions / check-lint-and-build

Replace `400,·401,·403,·404,·422` with `⏎············400,·401,·403,·404,·422,⏎··········`
},
},
CONFIG_SESSION_PHONE: {
CLIENT: process.env?.CONFIG_SESSION_PHONE_CLIENT || 'Evolution API',
Expand Down
Loading