Skip to main content
Webhooks let Platendoc push events to your server the moment a generation completes or fails — no polling required.

Setup

  1. Open app.platendoc.com and navigate to Webhooks in the sidebar.
  2. Click Add webhook.
  3. Enter your HTTPS endpoint URL and select the events you want to receive.
  4. Copy the signing secret — it’s shown only once. Store it in an environment variable (e.g. PLATENDOC_WEBHOOK_SECRET).

Events

EventWhen it fires
generation.completedDocument rendered successfully — outputUrl is ready
generation.failedRendering failed after all retries

Delivery format

Platendoc sends a POST request to your endpoint with the following headers:
Content-Type: application/json
X-Platendoc-Event: generation.completed
X-Platendoc-Signature: sha256=<hmac-sha256>

Payload

{
  "event": "generation.completed",
  "payload": {
    "generationId": "gen_01j8z..."
  },
  "timestamp": 1737036600000
}
For generation.failed events, payload also includes an error field:
{
  "event": "generation.failed",
  "payload": {
    "generationId": "gen_01j8z...",
    "error": "Template rendering failed: unknown variable 'foo'."
  },
  "timestamp": 1737036600000
}
After receiving the event, call GET /generations/{generationId} to fetch the full generation object including outputUrl.

Verifying signatures

Every delivery includes an X-Platendoc-Signature header. Always verify it before processing.
import { createHmac, timingSafeEqual } from 'node:crypto'

function verifySignature(rawBody: string, signature: string, secret: string): boolean {
  const expected = 'sha256=' + createHmac('sha256', secret).update(rawBody).digest('hex')
  try {
    return timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  } catch {
    return false
  }
}

// Express example
app.post('/webhooks/platendoc', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-platendoc-signature'] as string
  if (!verifySignature(req.body.toString(), signature, process.env.PLATENDOC_WEBHOOK_SECRET!)) {
    return res.status(401).send('Invalid signature')
  }

  const { event, payload } = JSON.parse(req.body.toString())
  // handle event...
  res.sendStatus(200)
})
Always use a timing-safe comparison (e.g. timingSafeEqual, hmac.compare_digest). Standard string equality (===, ==) is vulnerable to timing attacks.

Retries

AttemptDelay
1st retry1s
2nd retry2s
3rd retry4s
4th retry8s
5th retry16s
After 5 failed attempts the delivery is dropped. Return a 2xx status code quickly — perform any heavy processing asynchronously.
Respond with 200 OK immediately, then enqueue the event for processing. This prevents timeouts from causing unnecessary retries.