Webhooks

Outgoing webhooks let your systems react to MigrateTo events — generation finished, package built, member invited.

UI coming next
The end-user webhook management UI is on the roadmap. Today, webhooks are created via the API; the events listed below are emitted by the platform. Endpoint URL is in Settings → Developer.

Event catalog

  • generation.succeeded — a generation task finished. Payload: taskId, projectId, frameId, fidelity.
  • generation.failed — payload: taskId, error.
  • package.built — payload: packageId, projectId, downloadUrl, expiresAt.
  • member.invited / member.joined / member.removed.
  • secret.rotated — payload: integration, actorId.
  • audit.policy_changed.

Payload shape

POST <your-url>
Content-Type: application/json
X-MigrateTo-Event: generation.succeeded
X-MigrateTo-Signature: t=1750000000,v1=<hex>
X-MigrateTo-Delivery: 01J0...

{
  "id": "evt_01J...",
  "event": "generation.succeeded",
  "workspaceId": "ws_...",
  "createdAt": "2026-06-23T10:00:00Z",
  "data": {
    "taskId": "task_...",
    "projectId": "prj_...",
    "frameId": "frame_...",
    "fidelity": 92
  }
}

Verifying the signature

Compute HMAC-SHA256 over <timestamp>.<raw-body> using your endpoint secret. Reject if the timestamp is more than 5 minutes old or the signatures don't match.

import { createHmac, timingSafeEqual } from 'crypto'

const sig = req.headers['x-migrateto-signature']
const [tPart, v1Part] = sig.split(',')
const timestamp = tPart.split('=')[1]
const expected = createHmac('sha256', SECRET)
  .update(`${timestamp}.${rawBody}`)
  .digest('hex')
if (!timingSafeEqual(Buffer.from(v1Part.split('=')[1]), Buffer.from(expected))) {
  return res.status(401).end()
}

Retries

We retry on any non-2xx response with exponential backoff: 30s, 2m, 10m, 1h, 6h, 24h. After 24h we give up and mark the delivery failed. You can replay from Settings → Developer → Deliveries when that view ships.

Secret rotation

From the same screen you'll be able to rotate the signing secret. Rotation overlap is 24 hours — the platform signs every request with both the old and new secret so your verifier can accept either during the transition.

Was this page helpful?Send feedback →