Webhooks
Receive real-time HTTP notifications when booking events occur.
Webhooks let you receive real-time HTTP POST notifications when events occur in your Astrocal account. Instead of polling the API, register a webhook endpoint and Astrocal will push events to your server as they happen.
Event Types
| Event | Trigger |
|---|---|
booking.created | A new booking is created (may be pending payment) |
booking.confirmed | A booking transitions to confirmed (after payment, if paid) |
booking.cancelled | An existing booking is cancelled |
booking.rescheduled | An existing booking is rescheduled |
waitlist.promoted | A waitlist entry is promoted when a booking is cancelled |
waitlist.expired | A waitlist entry expires after its TTL |
Creating a Webhook Endpoint
Register a URL to receive events:
curl -X POST https://api.astrocal.dev/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/astrocal",
"events": ["booking.created", "booking.cancelled"]
}'const response = await fetch("https://api.astrocal.dev/v1/webhooks", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://your-app.com/webhooks/astrocal",
events: ["booking.created", "booking.cancelled"],
}),
});
const data = await response.json();Try it in the API playground →
The response includes a secret field. Save this immediately. The secret is only shown once at creation time and is used to verify webhook signatures.
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://your-app.com/webhooks/astrocal",
"events": ["booking.created", "booking.cancelled"],
"secret": "whsec_abc123...",
"active": true,
"created_at": "2026-03-01T10:00:00.000Z",
"updated_at": "2026-03-01T10:00:00.000Z"
}Payload Format
Each webhook delivery sends a JSON payload with this structure:
{
"event": "booking.created",
"data": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"organization_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type_id": "660e8400-e29b-41d4-a716-446655440000",
"status": "confirmed",
"start_time": "2026-03-15T14:00:00.000Z",
"end_time": "2026-03-15T14:30:00.000Z",
"invitee_name": "Jane Smith",
"invitee_email": "jane@example.com",
"invitee_timezone": "America/New_York",
"notes": null,
"created_at": "2026-03-01T10:00:00.000Z",
"updated_at": "2026-03-01T10:00:00.000Z"
},
"created_at": "2026-03-01T10:00:00.123Z"
}Verifying Signatures
Every webhook delivery includes an X-Astrocal-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature to ensure the webhook came from Astrocal and wasn't tampered with.
The header format is v1=<hex-digest>.
Node.js / TypeScript Example
import crypto from "node:crypto";
function verifyWebhookSignature(payload: string, signature: string, secret: string): boolean {
const expectedSig = crypto.createHmac("sha256", secret).update(payload).digest("hex");
const expected = `v1=${expectedSig}`;
// Use timing-safe comparison to prevent timing attacks
if (signature.length !== expected.length) return false;
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// In your webhook handler:
app.post("/webhooks/astrocal", (req, res) => {
const signature = req.headers["x-astrocal-signature"];
const rawBody = req.body; // Must be the raw string, not parsed JSON
if (!verifyWebhookSignature(rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
// Handle the event...
res.status(200).send("OK");
});Headers
Each webhook delivery includes these headers:
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Astrocal-Signature | HMAC-SHA256 signature (v1=<hex>) |
X-Astrocal-Event | The event type (e.g. booking.created) |
Retry Behavior
If your endpoint returns a non-2xx status code or the request times out (10 seconds), Astrocal will retry with exponential backoff:
| Attempt | Delay After |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 24 hours |
After 6 failed attempts, the delivery is marked as permanently failed. You can view delivery history via the API.
Managing Webhooks
You can manage webhooks from the dashboard or via the API.
Dashboard
The Webhooks page in the developer dashboard lets you:
- Create, edit, and delete webhook endpoints
- Toggle endpoints active/inactive
- View delivery logs with status, HTTP response codes, and retry counts
- Copy the signing secret on creation
Navigate to Dashboard > Webhooks to get started.
List Endpoints
curl https://api.astrocal.dev/v1/webhooks \
-H "Authorization: Bearer YOUR_API_KEY"const response = await fetch("https://api.astrocal.dev/v1/webhooks", {
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
});
const data = await response.json();Try it in the API playground →
Update an Endpoint
curl -X PATCH https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"active": false}'const response = await fetch(
"https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID",
{
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ active: false }),
}
);
const data = await response.json();Try it in the API playground →
Delete an Endpoint
curl -X DELETE https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID \
-H "Authorization: Bearer YOUR_API_KEY"const response = await fetch(
"https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID",
{
method: "DELETE",
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
}
);Try it in the API playground →
View Delivery History
curl "https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID/deliveries?status=failed" \
-H "Authorization: Bearer YOUR_API_KEY"const response = await fetch(
"https://api.astrocal.dev/v1/webhooks/WEBHOOK_ID/deliveries?status=failed",
{
headers: {
Authorization: "Bearer YOUR_API_KEY",
},
}
);
const data = await response.json();Try it in the API playground →
Best Practices
- Always verify signatures. Never trust webhook payloads without signature verification.
- Respond quickly. Return a 2xx response within 10 seconds. Process the event asynchronously if needed.
- Handle duplicates. In rare cases, the same event may be delivered more than once. Use the event
data.idfor idempotency. - Use HTTPS. Always use HTTPS URLs for your webhook endpoints in production.
- Monitor deliveries. Check the dashboard or the deliveries endpoint for failed deliveries.