← Integrations
Notifications

Seshn + Slack

Post booking notifications to Slack channels. New bookings, cancellations, payment alerts — right where your team already works.

Setup

No SDK required. Slack Incoming Webhooks accept a simple HTTP POST — you can wire this up with just fetch.

  1. Go to your Slack workspace settings and create an Incoming Webhook.
  2. Choose the channel (e.g. #bookings).
  3. Copy the webhook URL.

Webhook events

Choose which events matter for your team's ops:

booking.createdNew booking notification
booking.cancelledAlert on cancellations
payment.failedPayment issues that need attention
waitlist.joinedDemand signal for popular slots

Webhook handler

app/api/webhooks/seshn/route.ts
1import { createHmac } from 'crypto';
2
3const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL!;
4const WEBHOOK_SECRET = process.env.SESHN_WEBHOOK_SECRET!;
5
6export async function POST(req: Request) {
7 const body = await req.text();
8 const signature = req.headers.get('x-webhook-signature');
9
10 const expected = 'sha256=' +
11 createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex');
12
13 if (signature !== expected) {
14 return new Response('Invalid signature', { status: 401 });
15 }
16
17 const event = req.headers.get('x-webhook-event');
18 const payload = JSON.parse(body);
19
20 let text: string | null = null;
21
22 switch (event) {
23 case 'booking.created':
24 const date = new Date(payload.slot.startTime);
25 text = [
26 '*New booking*',
27 `>${payload.service.name}`,
28 `>${payload.contact.name} · ${payload.seats} seat(s)`,
29 `>${date.toLocaleDateString()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`,
30 ].join('\n');
31 break;
32
33 case 'booking.cancelled':
34 text = [
35 '*Booking cancelled*',
36 `>${payload.service.name}`,
37 `>${payload.contact.name}`,
38 payload.reason ? `>Reason: ${payload.reason}` : null,
39 ].filter(Boolean).join('\n');
40 break;
41
42 case 'payment.failed':
43 text = [
44 '*Payment failed*',
45 `>Booking ${payload.bookingId}`,
46 `>${payload.contact.name} · ${payload.contact.email}`,
47 `>Amount: ${(payload.amount / 100).toFixed(2)} ${payload.currency.toUpperCase()}`,
48 ].join('\n');
49 break;
50
51 case 'waitlist.joined':
52 text = [
53 '*Waitlist joined*',
54 `>${payload.service.name}`,
55 `>${payload.contact.name} is waiting for a spot`,
56 ].join('\n');
57 break;
58 }
59
60 if (text) {
61 await fetch(SLACK_WEBHOOK_URL, {
62 method: 'POST',
63 headers: { 'Content-Type': 'application/json' },
64 body: JSON.stringify({ text }),
65 });
66 }
67
68 return new Response('OK', { status: 200 });
69}

Register the webhook

terminal
1curl -X POST https://api.seshn.net/v1/webhooks \
2 -H "Authorization: Bearer sk_live_..." \
3 -H "Content-Type: application/json" \
4 -d '{
5 "url": "https://yourdomain.com/api/webhooks/seshn",
6 "events": [
7 "booking.created",
8 "booking.cancelled",
9 "payment.failed",
10 "waitlist.joined"
11 ]
12 }'

Next steps

  • Use Block Kit for richer message formatting with buttons and fields.
  • Route different events to different channels — e.g. #bookings for new bookings, #alerts for payment failures.
  • Add a daily digest using a cron job that queries GET /v1/bookings and posts a summary.