Webhooks

Get notified when transformation runs finish — with a webhook secret header you can verify.

Webhooks are the recommended way to integrate with Transmutify: instead of polling for state changes, your endpoint is called when final output succeeds, a run fails terminally, or a reviewer cancels a hosted review — with the full run state included in the payload.

Setting up

Pass a notify_url when creating a transformation run. Transmutify will POST a JSON payload to that URL when final output completes successfully, when a run fails terminally, and when a reviewer explicitly cancels a hosted review. In production the URL must use HTTPS. Review readiness and edits are handled inside the hosted Review UI; use the review_url from create or full transformation responses to hand reviewers into that flow.

Default webhook URL

You don't have to send notify_url on every request. Set a default webhook URL once under Settings → Webhook in the dashboard and Transmutify falls back to it for any run created without an explicit notify_url. A notify_url on the request always takes precedence, so you can keep an org-wide default and override it per run when needed. The same screen is where your webhook secret lives — copy it for verification (below) and rotate it there if it leaks.

Events

transformation.finishedevent

The final run completed successfully and the output is ready to download.

transformation.review.canceledevent

A reviewer canceled the hosted review. The review is closed and remains available read-only.

transformation.failedevent

The run failed terminally and cannot be completed — for example the uploaded file was invalid or oversized, a raw or image extraction failed, or final output generation failed after approval. Recoverable review issues (such as a failed AI mapping suggestion that a reviewer can fix by mapping manually) do not trigger this event.

Payload

Every delivery carries the run's current state, including your metadata, the hosted review state, the effective mapping rule, and — when output is ready — a pre-signed download_url. Review cancellation events are informational and include download_url: null. Terminal failures deliver transformation.failed with the error object describing what went wrong; you can also fetch GET /transformations/{id} at any time to read the persisted error payload.

transformation.finished
{
  "event": "transformation.finished",
  "id": "9b2e1f6c-8e6d-4a3b-9c1f-7d2e5a8b3c4d",
  "status": "completed",
  "stage": "final",
  "schema_id": "<schema-id>",
  "metadata": { "import_id": "batch-42" },
  "review_url": "https://review.transmutify.io/reviews/…secret…",
  "review_state": "approved",
  "review_canceled_at": null,
  "review_approved_at": "2026-01-15T12:33:10+00:00",
  "review_expires_at": "2026-01-16T12:00:00Z",
  "mapping_rule": { "first_name": "First Name", "email": "E-Mail" },
  "source_headers": ["First Name", "E-Mail"],
  "completed_at": "2026-01-15T12:34:56+00:00",
  "failed_at": null,
  "error": null,
  "download_url": "https://storage.example.com/…signed…"
}
transformation.review.canceled
{
  "event": "transformation.review.canceled",
  "id": "9b2e1f6c-8e6d-4a3b-9c1f-7d2e5a8b3c4d",
  "status": "completed",
  "stage": "review",
  "schema_id": "<schema-id>",
  "metadata": { "external_id": "crm-import-2026-01-15" },
  "review_url": "https://review.transmutify.io/reviews/…secret…",
  "review_state": "canceled",
  "review_canceled_at": "2026-01-15T12:18:30+00:00",
  "review_approved_at": null,
  "review_expires_at": "2026-01-16T12:00:00Z",
  "mapping_rule": null,
  "source_headers": [],
  "completed_at": "2026-01-15T12:14:04+00:00",
  "failed_at": null,
  "error": null,
  "download_url": null
}
transformation.failed
{
  "event": "transformation.failed",
  "id": "9b2e1f6c-8e6d-4a3b-9c1f-7d2e5a8b3c4d",
  "status": "failed",
  "stage": "final",
  "schema_id": "<schema-id>",
  "metadata": { "import_id": "batch-42" },
  "review_url": "https://review.transmutify.io/reviews/…secret…",
  "review_state": null
  "review_canceled_at": null,
  "review_expires_at": "2026-01-16T12:00:00Z",
  "mapping_rule": { "first_name": "First Name", "email": "E-Mail" },
  "source_headers": ["First Name", "E-Mail"],
  "completed_at": null,
  "failed_at": "2026-01-15T12:34:56+00:00",
  "error": {
    "code": "transformation_storage_failed",
    "message": "The transformation could not read or write one of its files.",
    "type": "storage",
    "stage": "final_processing",
    "retryable": true,
    "context": []
  },
  "download_url": null
}

Headers and webhook secret

  • X-Transmutify-Event — the event name.
  • X-Transmutify-Id — the transformation run UUID.
  • X-Transmutify-Webhook-Secret — your organization's webhook secret (shown in the dashboard).

Verify the webhook secret before trusting a delivery:

Node.js
const crypto = require('node:crypto');

function verifyTransmutifyWebhookSecret(secretHeader, webhookSecret) {
    const received = Buffer.from(secretHeader || '');
    const expected = Buffer.from(webhookSecret);

    return received.length === expected.length
        && crypto.timingSafeEqual(received, expected);
}
PHP
<?php

function verifyTransmutifyWebhookSecret(
    string $secretHeader,
    string $webhookSecret,
): bool {
    return hash_equals($webhookSecret, $secretHeader);
}

Delivery and retries

Respond with any 2xx status to acknowledge the delivery. Transmutify attempts each delivery up to 3 times with a 15-second response timeout. The run's notification_status field reflects the latest successful final-output webhook delivery outcome (pending, delivered, or failed).

Webhooks are delivered at-least-once and can arrive out of order. Make your handler idempotent — the X-Transmutify-Id header plus the event name is a good deduplication key.