Webhooks overview
Outgoing webhooks for slideshow events, plus the Stripe billing events we consume.
Register HTTPS endpoints from /integrations/webhooks (UI) or POST /v0/api/webhooks (API). Each delivery is HMAC-SHA256 signed with your per-endpoint secret and carries the X-ViralSlides-Signature, X-ViralSlides-Event, and X-ViralSlides-Delivery-Id headers. Failed deliveries retry on a 1m / 5m / 30m / 2h / 12h backoff for up to six attempts.
Available events
- slideshow.completed — generation finished; the payload carries the rendered slide image URLs, a thumbnail, and the share link.
- slideshow.failed — generation failed (payload includes the error message; URL fields are empty).
- quota.exceeded — the org hit its monthly slideshow / hook-refresh / apps quota.
{
"slideshow_id": "ss_01HQ...",
"organization_id": "org_01HQ...",
"app_id": "app_01HQ...",
"app_name": "Tidy",
"status": "COMPLETED",
"hook_text": "POV: your app feed but tidier",
"language": "en",
"slide_count": 6,
"template_set": "default",
"thumbnail_url": "https://cdn.viralslides.app/orgs/org_01HQ.../slideshows/ss_01HQ.../slide-0.png",
"image_urls": [
"https://cdn.viralslides.app/orgs/org_01HQ.../slideshows/ss_01HQ.../slide-0.png",
"https://cdn.viralslides.app/orgs/org_01HQ.../slideshows/ss_01HQ.../slide-1.png"
],
"slides": [
{
"order": 0,
"template": "HOOK",
"layout_type": "hook-title",
"title": "POV: your app feed but tidier",
"subtitle": "",
"label": "POV",
"image_url": "https://cdn.viralslides.app/orgs/org_01HQ.../slideshows/ss_01HQ.../slide-0.png",
"duration": 3
}
],
"share_url": "https://viralslides.app/share/shr_01HQ..."
}URLs you can act on are included so you don't need a follow-up API call: `image_urls` is the ordered list of rendered slide PNGs, `slides` carries the same images plus per-slide metadata (order, template, layout, copy, duration), `thumbnail_url` is the first slide, and `share_url` is the public share page (null when sharing is disabled for the slideshow). To download everything as a ZIP, POST to the share endpoint: `POST /v0/public/share/{token}/zip`. On `slideshow.failed`, the URL fields are present but empty (no slides were rendered) and an `error` field describes the failure.
Verifying signatures
Compute HMAC-SHA256 of the raw request body using your endpoint secret and compare against the hex digest in X-ViralSlides-Signature (formatted as `sha256=<hex>`). Reject the request if the signatures don't match in constant time.
Stripe events we consume
Our Stripe webhook endpoint (https://api.viralslides.app/v0/public/billing/stripe-webhook) handles the events below. Each is signed and verified via STRIPE_WEBHOOK_SECRET; duplicates are deduped by event id.
| Event | Effect |
|---|---|
| customer.subscription.created | Creates a local subscription and mirrors plan + status onto the organization. |
| customer.subscription.updated | Upserts the subscription. Status maps to ACTIVE / TRIALING / PAST_DUE / CANCELED. |
| customer.subscription.deleted | Marks the subscription CANCELED and suspends the organization. Triggers a notification email. |
| invoice.payment_failed | Sets the org status to PAST_DUE and emails the owner the amount due. |
| invoice.payment_succeeded | Reactivates the org (ACTIVE) and rolls over usage_counters for the new billing period. |
| checkout.session.completed | Captures stripe_customer_id on the organization for fast linkage of the first checkout. |
Idempotency
Every Stripe event id is recorded in processed_webhook_events on first delivery and short-circuited on subsequent retries. This means it's safe for Stripe to redeliver events — we'll just acknowledge with received: true, duplicate: true.
Share & oEmbed
Public share links are issued per slideshow from the Slideshows page or POST /v0/api/slideshows/:id/share. Tokens are 32-char base64url, unguessable, and can be rotated or password-protected. Share URLs follow the pattern https://viralslides.app/share/<token>; the same token is valid in the iframe-safe embed at /embed/<token>. oEmbed discovery is exposed at GET /v0/public/oembed?url=<share-url>&format=json.
Slack & Discord notifications
Add incoming webhook URLs from Slack or Discord at /integrations/notify. ViralSlides will format slideshow.completed / slideshow.failed / quota.exceeded events into native Slack attachments or Discord embeds and POST them to your channel. Test-send from the UI before relying on the channel in production.
MCP server
The Model Context Protocol (MCP) server lets AI clients (Claude Code, Claude Desktop, Cursor, and anything that speaks MCP) list apps, browse hooks, and generate or publish slideshows through tool calls. It reuses the same vs_live_ API key as the REST API. See the dedicated MCP server guide in Webhooks & integrations for setup (use --scope user so it persists) and the full tool list.
