Receive real-time events from ShipPulse in your application.
ShipPulse sends HTTP POST requests to your endpoint whenever specific events occur in your project — such as a new testimonial, incident, or monitor status change. You can use webhooks to:
All webhook payloads are JSON with this structure:
{
"event": "testimonial.received",
"timestamp": "2026-04-01T12:00:00.000Z",
"project": {
"id": "uuid",
"name": "My SaaS",
"slug": "my-saas"
},
"data": {
"id": "uuid",
"authorName": "Sarah Chen",
"content": "Incredible product!",
"rating": 5
},
"title": "⭐ New testimonial from Sarah Chen",
"body": "Incredible product!",
"url": "https://shippulse.dev/dashboard/projects/my-saas/testimonials"
}When you configure a webhook secret, ShipPulse signs each payload using HMAC-SHA256 and sends the signature in the X-ShipPulse-Signature header:
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
X-ShipPulse-Event: testimonial.received
X-ShipPulse-Signature: sha256=abc123...
User-Agent: ShipPulse-Webhook/1.0Verify the signature to ensure the request came from ShipPulse:
import { createHmac } from "crypto"
function verifySignature(
rawBody: string,
signature: string,
secret: string
): boolean {
const expected = `sha256=${createHmac("sha256", secret)
.update(rawBody)
.digest("hex")}`
// Constant-time comparison (prevents timing attacks)
if (expected.length !== signature.length) return false
let diff = 0
for (let i = 0; i < expected.length; i++) {
diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i)
}
return diff === 0
}Or use the @shippulse/node SDK:
import { ShipPulseNode } from "@shippulse/node"
const sp = new ShipPulseNode({ apiKey: process.env.SHIPPULSE_API_KEY! })
// Express / Hono / Elysia handler
app.post("/webhooks/shippulse", async (req) => {
const raw = await req.text()
const sig = req.headers.get("x-shippulse-signature") ?? ""
if (!sp.verifyWebhook(raw, sig, process.env.SHIPPULSE_WEBHOOK_SECRET!)) {
return new Response("Unauthorized", { status: 401 })
}
const event = sp.parseWebhook(raw)
console.log("Received:", event.event, event.data)
return new Response("OK")
})If your endpoint does not return a 2xx response, ShipPulse retries with exponential backoff:
| Attempt | Delay | Type |
|---|---|---|
| 1st | Immediate | In-process |
| 2nd | +1 second | In-process |
| 3rd | +5 seconds | In-process |
| 4th | +1 minute | Cron |
| 5th | +5 minutes | Cron |
| 6th | +30 minutes | Cron |
| 7th | +2 hours | Cron |
| 8th | +12 hours | Cron |
| 9th (final) | +24 hours | Cron |
After 9 attempts, the delivery is marked as permanently failed. You can manually resend from the delivery log in your notification channel settings.
Every webhook attempt (success or failure) is logged. View the delivery history at Dashboard → Project Settings → Notifications → History.
You can resend any past delivery from the history view, which is useful for replaying events into a new integration.
| Event | Trigger | Key data |
|---|---|---|
| incident.created | A new incident is opened | title, status, impact, body |
| incident.resolved | An incident is marked as resolved | title, resolved_at |
| incident.updated | An incident update is posted | title, status, update |
| monitor.down | A monitor transitions to down | name, url, status |
| monitor.up | A monitor recovers to up | name, url, status, downtime_ms |
| maintenance.scheduled | Maintenance window is scheduled | title, scheduled_start, scheduled_end |
| maintenance.started | Maintenance window begins | title, scheduled_end |
| maintenance.completed | Maintenance window ends | title |
| changelog.published | A changelog entry is published | title, slug, type |
| testimonial.received | A new testimonial is submitted | author_name, content, rating |
| testimonial.approved | A testimonial is approved | author_name, testimonial_id |
| testimonial.rejected | A testimonial is rejected | author_name, testimonial_id |
| feedback.created | A feedback post is submitted | title, author_name, board_name |
| feedback.voted | A feedback post reaches a vote milestone (every 5 votes) | title, vote_count |
| feedback.status_changed | A feedback post status changes | title, from_status, to_status |
| feedback.commented | A comment is posted on a feedback post | post_title, author_name, is_official |
timestamp and data.id fields for deduplication.