2026-05-21 19:45:08 +02:00
|
|
|
import type { EventPayload, DispatchResult } from '../types';
|
|
|
|
|
|
|
|
|
|
type WebhookConfig = {
|
|
|
|
|
webhookUrl: string;
|
|
|
|
|
webhookHeaders?: { key: string; value: string }[];
|
|
|
|
|
webhookSecret?: string;
|
|
|
|
|
webhookSecretHeader?: string;
|
|
|
|
|
};
|
2026-01-07 13:33:23 +01:00
|
|
|
|
|
|
|
|
export async function sendWebhook(
|
2026-05-21 19:45:08 +02:00
|
|
|
config: WebhookConfig,
|
2026-01-07 13:33:23 +01:00
|
|
|
payload: EventPayload
|
|
|
|
|
): Promise<DispatchResult> {
|
2026-05-21 19:45:08 +02:00
|
|
|
const { webhookUrl, webhookHeaders, webhookSecret, webhookSecretHeader } = config;
|
2026-01-07 13:33:23 +01:00
|
|
|
|
|
|
|
|
const headers: Record<string, string> = {
|
|
|
|
|
'Content-Type': 'application/json',
|
2026-05-21 19:45:08 +02:00
|
|
|
'User-Agent': 'Portabase-Notifier/1.0',
|
2026-01-07 13:33:23 +01:00
|
|
|
};
|
|
|
|
|
|
2026-05-21 19:45:08 +02:00
|
|
|
// New format: iterate custom headers array
|
|
|
|
|
if (webhookHeaders && webhookHeaders.length > 0) {
|
|
|
|
|
const RESERVED = new Set(['content-type', 'user-agent']);
|
|
|
|
|
for (const { key, value } of webhookHeaders) {
|
|
|
|
|
if (key && !RESERVED.has(key.toLowerCase())) headers[key] = value;
|
|
|
|
|
}
|
|
|
|
|
} else if (webhookSecret) {
|
|
|
|
|
// Legacy format: single secret header pair
|
2026-01-07 13:33:23 +01:00
|
|
|
headers[webhookSecretHeader || 'X-Webhook-Secret'] = webhookSecret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = await fetch(webhookUrl, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: JSON.stringify(payload),
|
2026-05-21 19:45:08 +02:00
|
|
|
headers,
|
2026-01-07 13:33:23 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.text();
|
|
|
|
|
throw new Error(`Webhook error: ${res.status} ${err}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
provider: 'webhook',
|
|
|
|
|
message: 'Sent to Webhook',
|
|
|
|
|
response: await res.text(),
|
|
|
|
|
};
|
|
|
|
|
}
|