Skip to content

Commit 863aac0

Browse files
authored
Merge pull request #191 from vernu/webhooks-improvements
auto-disable webhook subscriptions with high failure rate and increase delivery request timeout
2 parents fd4969c + 6e7ed42 commit 863aac0

File tree

6 files changed

+324
-8
lines changed

6 files changed

+324
-8
lines changed

api/.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ MAIL_USER=
2424
MAIL_PASS=
2525
MAIL_FROM=
2626
MAIL_REPLY_TO=
27+
ADMIN_EMAIL=
28+
29+
# Webhook delivery HTTP timeout in milliseconds (default 30000, min 10000, max 60000)
30+
WEBHOOK_DELIVERY_TIMEOUT_MS=30000
31+
32+
# Auto-disable webhook subscriptions with high failure rate (cron runs daily)
33+
WEBHOOK_AUTO_DISABLE_FAILURE_THRESHOLD=50
34+
WEBHOOK_AUTO_DISABLE_LOOKBACK_DAYS=30
2735

2836
# SMS Queue Configuration
2937
USE_SMS_QUEUE=false
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<html>
2+
<head>
3+
<meta charset='utf-8' />
4+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
5+
<title>{{title}}{{runAt}}</title>
6+
<style>
7+
body { font-family: Arial, sans-serif; line-height: 1.5; color: #333; margin: 0; padding: 0; }
8+
.container { max-width: 900px; margin: 0 auto; padding: 20px; }
9+
.header { padding: 16px 0; border-bottom: 1px solid #eee; }
10+
.title { font-size: 18px; font-weight: bold; }
11+
.meta { font-size: 12px; color: #666; margin-top: 4px; }
12+
table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
13+
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
14+
th { background-color: #f5f5f5; font-weight: 600; }
15+
.url { word-break: break-all; max-width: 240px; }
16+
.footer { font-size: 12px; color: #777; margin-top: 24px; padding-top: 16px; border-top: 1px solid #eee; }
17+
</style>
18+
</head>
19+
<body>
20+
<div class='container'>
21+
<div class='header'>
22+
<div class='title'>{{title}}</div>
23+
<div class='meta'>Run at {{runAt}} · {{count}} subscription(s) auto-disabled</div>
24+
</div>
25+
<table>
26+
<thead>
27+
<tr>
28+
<th>Subscription ID</th>
29+
<th>Delivery URL</th>
30+
<th>Failed count</th>
31+
<th>Period (days)</th>
32+
<th>User name</th>
33+
<th>User email</th>
34+
</tr>
35+
</thead>
36+
<tbody>
37+
{{#each disabledList}}
38+
<tr>
39+
<td>{{this.subscriptionId}}</td>
40+
<td class='url'>{{this.deliveryUrl}}</td>
41+
<td>{{this.failureCount}}</td>
42+
<td>{{this.lookbackDays}}</td>
43+
<td>{{this.userName}}</td>
44+
<td>{{this.userEmail}}</td>
45+
</tr>
46+
{{/each}}
47+
</tbody>
48+
</table>
49+
<div class='footer'>{{brandName}} – Webhook auto-disable cron</div>
50+
</div>
51+
</body>
52+
</html>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<html lang='en' xmlns:v='urn:schemas-microsoft-com:vml' xmlns:o='urn:schemas-microsoft-com:office:office'>
2+
<head>
3+
<meta charset='utf-8' />
4+
<meta name='viewport' content='width=device-width, initial-scale=1' />
5+
<meta http-equiv='x-ua-compatible' content='ie=edge' />
6+
<title>{{title}}{{brandName}}</title>
7+
<style>
8+
.preheader { display:none !important; visibility:hidden; opacity:0; color:transparent; height:0; width:0; overflow:hidden; mso-hide:all; }
9+
@media screen and (max-width: 600px) { .container { width:100% !important; } .stack { display:block !important; width:100% !important; } .p-sm { padding:16px !important; } .text-center-sm { text-align:center !important; } .hide-sm { display:none !important; } }
10+
</style>
11+
<!--[if mso]>
12+
<style type="text/css"> body, table, td, a { font-family: Helvetica, Arial, sans-serif !important; } </style>
13+
<![endif]-->
14+
</head>
15+
<body style='margin:0; padding:0; background:#f7f9fc;'>
16+
<div class='preheader'>Webhook subscription disabled – {{brandName}}</div>
17+
<table role='presentation' cellpadding='0' cellspacing='0' border='0' width='100%' class='email-bg' style='background:#f7f9fc;'>
18+
<tr>
19+
<td align='center' style='padding:24px;'>
20+
<table role='presentation' cellpadding='0' cellspacing='0' border='0' width='600' class='container' style='width:600px; max-width:600px;'>
21+
<tr>
22+
<td style='padding:12px 16px 0 16px;'>
23+
<table role='presentation' width='100%' cellspacing='0' cellpadding='0' border='0'>
24+
<tr>
25+
<td class='stack' valign='middle' style='padding:8px 0;'>
26+
<table role='presentation' cellspacing='0' cellpadding='0' border='0'>
27+
<tr>
28+
<td valign='middle' style='padding-right:10px;'>
29+
<img src='https://textbee.dev/images/logo.png' alt='{{brandName}}' width='36' height='36' style='display:block; border:0; outline:none; text-decoration:none;' />
30+
</td>
31+
<td valign='middle' style='font:600 18px Arial, Helvetica, sans-serif; color:#EA580C;'>{{brandName}}</td>
32+
</tr>
33+
</table>
34+
</td>
35+
<td class='stack text-center-sm' valign='middle' align='right' style='padding:8px 0;'>
36+
</td>
37+
</tr>
38+
</table>
39+
</td>
40+
</tr>
41+
<tr>
42+
<td align='center' style='padding:16px 16px 0 16px;'>
43+
<table role='presentation' width='100%' cellspacing='0' cellpadding='0' border='0' class='card' style='background:#ffffff; border-radius:10px;'>
44+
<tr>
45+
<td align='center' style='background:#F97316; border-radius:10px 10px 0 0; padding:28px 20px;'>
46+
<div style='font:700 24px Arial, Helvetica, sans-serif; color:#ffffff;'>{{title}}</div>
47+
</td>
48+
</tr>
49+
<tr>
50+
<td class='p-sm' style='padding:28px; font:16px/1.6 Arial, Helvetica, sans-serif; color:#111827;'>
51+
<div style='font:700 18px Arial, Helvetica, sans-serif; color:#111827; margin-bottom:8px;'>Hi {{name}},</div>
52+
<p style='margin:0 0 16px 0;'>{{message}}</p>
53+
<div style='text-align:center; padding:8px 0 2px;'>
54+
<!--[if mso]>
55+
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{ctaUrl}}" style="height:48px;v-text-anchor:middle;width:260px;" arcsize="10%" strokecolor="#EA580C" fillcolor="#F97316">
56+
<w:anchorlock/>
57+
<center style="color:#ffffff;font-family:Arial, Helvetica, sans-serif;font-size:16px;font-weight:bold;">{{ctaLabel}}</center>
58+
</v:roundrect>
59+
<![endif]-->
60+
<!--[if !mso]><!-- -->
61+
<a href='{{ctaUrl}}' style='background:#F97316; border:1px solid #EA580C; border-radius:6px; color:#ffffff; display:inline-block; font:700 16px Arial, Helvetica, sans-serif; line-height:48px; text-align:center; text-decoration:none; width:260px;'>{{ctaLabel}}</a>
62+
<!--<![endif]-->
63+
</div>
64+
</td>
65+
</tr>
66+
</table>
67+
</td>
68+
</tr>
69+
<tr>
70+
<td align='center' style='padding:16px;'>
71+
<table role='presentation' width='100%' cellspacing='0' cellpadding='0' border='0'>
72+
<tr>
73+
<td align='center' style='font:12px/1.6 Arial, Helvetica, sans-serif; color:#6b7280;'>
74+
<div>© 2025 {{brandName}}. All rights reserved.</div>
75+
<div class='muted' style='margin-top:4px;'>Manage webhooks in <a href='https://app.textbee.dev/dashboard/account/' style='color:#6b7280; text-decoration:underline;'>Account settings</a>.</div>
76+
</td>
77+
</tr>
78+
</table>
79+
</td>
80+
</tr>
81+
</table>
82+
</td>
83+
</tr>
84+
</table>
85+
</body>
86+
</html>

api/src/webhook/schemas/webhook-subscription.schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ export class WebhookSubscription {
4040

4141
@Prop({ type: Date })
4242
lastDeliveryFailureAt: Date
43+
44+
@Prop({
45+
type: [{ at: { type: Date }, text: { type: String } }],
46+
default: [],
47+
})
48+
notes: { at: Date; text: string }[]
4349
}
4450

4551
export const WebhookSubscriptionSchema =

api/src/webhook/webhook.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from './schemas/webhook-notification.schema'
1414
import { AuthModule } from 'src/auth/auth.module'
1515
import { UsersModule } from 'src/users/users.module'
16+
import { MailModule } from 'src/mail/mail.module'
1617
import { WebhookQueueService } from './queue/webhook-queue.service'
1718
import { WebhookQueueProcessor } from './queue/webhook-queue.processor'
1819

@@ -38,6 +39,7 @@ import { WebhookQueueProcessor } from './queue/webhook-queue.processor'
3839
}),
3940
AuthModule,
4041
UsersModule,
42+
MailModule,
4143
],
4244
controllers: [WebhookController],
4345
providers: [WebhookService, WebhookQueueService, WebhookQueueProcessor],

0 commit comments

Comments
 (0)