Transloadit
Pricing
  • File Uploads
  • File Importing
  • Batch Processing
  • Video Encoding
  • Audio Encoding
  • Image Processing
  • Document Processing
  • Artificial Intelligence
  • File Filtering & Security
  • Media Cataloging
  • File Compression
  • Code Evaluation
  • File Exporting
  • Smart CDN
  • View all services
  • Explore integrations
  • Explore live demos
  • Uppy
  • TransloaditKit
  • Android SDK
  • Node.js SDK
  • Python SDK
  • Ruby SDK
  • Go SDK
  • Java SDK
  • PHP SDK
  • Zapier
  • MCP Server
  • Transloadit CLI
  • Terraform
  • Essentials
  • Best Practices
  • FAQ
  • Robots
  • API
  • Formats
  • Build your first app
  • About
  • Comparisons
  • Open Source
  • Testimonials
  • Jobs
  • Security
  • Posts
  • DevTimes
  • DevTips
  • Press
  • Research
  • Case Studies
  • Solutions
  • Guides
  • Glossary
  • Legal
  • Tools
  • Helping Coursera bring education to millions around the world
  • Transloadit Support
  • Open Source Support
  • Service level agreement
EssentialsRobotsFAQAPIFormatsBest Practices
Getting started
  • Overview
  • My first App
  • Saving result files
Topics
  • Assembly Instructions
  • Assembly Variables
  • Dynamic Evaluation
  • Templates
  • Webhooks
  • Third party Credentials
  • Builtin Templates
  • Resize Strategies
  • Assembly Execution Progress
  • Workspaces
  • AI Agents
  • Advanced use Parameter
  • The ignore_errors Parameter
Software Development Kits
  • Overview
  • Android SDK
  • Browsers
  • Convex
  • cURL
  • Go SDK
  • Java SDK
  • MCP Server
  • Multipart Form
  • Node.js SDK
  • PHP SDK
  • Python SDK
  • Ruby SDK
  • Terraform
  • TransloaditKit
  • Zapier Integration
Migration
  • Migrate from Cloudinary to Transloadit
  • Migrate from Uploadcare to Transloadit
  • Migrate from Mux to Transloadit
  • Migrate from Filestack to Transloadit

Webhooks

Instead of waiting for an Assembly to finish and its API request to respond, you can also configure a Webhook, also known as an Assembly Notification. The system will send a POST request to a URL of your choosing containing a full report once an Assembly ends.

Why use Webhooks?

By choosing to use Webhooks you enable your end-users to have a smoother experience, as they only need to wait for file uploads to finish before they can close the browser window. Without file uploads, they could even close the browser window right away.

How to activate Webhooks?

You activate webhooks by adding notify_url to your Assembly Instructions in your Template on the same JSON level as steps:

{
  "steps": {
    // …
  },
  "notify_url": "https://example.com/transloadit_pingback"
}

When you then run your Template Transloadit is going to inform your back-end once all processing has happened by sending a POST request to that defined URL containing all the Assembly status json.

If you don't want your users/program to wait for encoding, this often also involves setting a flag. In the case of Uppy, set the waitForEncoding parameter to false. In many back-end SDKs, waiting for encoding involves explicitly polling the Assembly Status, so just refraining from that will do the trick.

Your back-end needs to respond with a 200 header, otherwise Transloadit assumes the Notification has failed and retries it a few times with exponential backoff.

Customize the Notification payload

By default, webhook requests include the full Assembly Status JSON. If your Assemblies produce many results, you can reduce payload size with notification_payload.

Add it alongside notify_url:

{
  "steps": {
    // …
  },
  "notify_url": "https://example.com/transloadit_pingback",
  "notification_payload": ["without_results", "without_upload_meta_data"]
}
  • notification_payload

    Array<without_params | without_result_meta_data | without_results | without_upload_meta_data | without_uploads>

    Controls the size of the payload sent in Assembly notifications (both the initial notification and any notification replays). An empty array (default) sends the complete Assembly status. Add "without_params" to strip raw Assembly instruction JSON fields (params, template, and merged_params). Add "without_upload_meta_data" to strip meta from files in uploads. Add "without_result_meta_data" to strip meta from files in results. Add "without_uploads" to strip uploads entirely. Add "without_results" to strip results entirely. Filtering is applied before serialization, so using these options reduces memory use and helps avoid oversized-payload or OOM failures on large assemblies.

You can combine multiple values in the same array. This also applies when you replay notifications from the Assembly page.

What does this POST request look like?

This multipart POST request contains a field called transloadit, which contains the full Assembly Status JSON. You can find an example of this in our API Response docs. It will also contain a signature field. Verify it with the Auth Secret belonging to the Auth Key used for the original Assembly before trusting the payload. Verification must use the exact transloadit field string, before parsing or reserializing its JSON.

Code Example

Let's assume you had indeed specified "notify_url": "https://example.com/transloadit_pingback" and that the back-end server that would accept incoming POSTs there was written in Node.js.

Note

This example shows how to verify incoming webhook signatures from Transloadit, which is different from generating signatures for API requests. For creating Assemblies with automatic signature generation, use our SDKs instead. If you'd like to see how signature verification works under the hood, you can view the signature utilities source code⁠.

This example uses Node.js 24 or newer, its built-in multipart parser, and the official signature verifier. Install its dependencies:

yarn add @transloadit/utils@4.7.1 zod@3.25.76

Use an ES module project ("type": "module" in package.json) and save the following as notification-backend-node.ts:

import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'

import { verifyWebhookSignature } from '@transloadit/utils'
import { z } from 'zod'

const authSecret = process.env.AUTH_SECRET
if (!authSecret) throw new Error('Set AUTH_SECRET to the secret used for the original Assembly')

const port = Number(process.argv[2] ?? '3020')
if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('Invalid port')

const maxBodyBytes = 1024 * 1024
const notificationSchema = z.object({ assembly_id: z.string().regex(/^[a-f0-9]{32}$/u) })

function respond(res: ServerResponse, status: number, message: string): void {
  res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8', Connection: 'close' })
  res.end(message)
}

async function receive(req: IncomingMessage, res: ServerResponse, secret: string): Promise<void> {
  if (req.url !== '/transloadit_pingback' || req.method !== 'POST') {
    return respond(res, 404, 'Not found')
  }
  const contentType = req.headers['content-type'] ?? ''
  if (contentType.split(';')[0].trim().toLowerCase() !== 'multipart/form-data') {
    return respond(res, 415, 'Expected multipart form data')
  }

  const chunks: Buffer[] = []
  let bytes = 0
  // Keep the socket open long enough to send 413 when rejecting a streaming request.
  for await (const chunk of req.iterator({ destroyOnReturn: false })) {
    if (!Buffer.isBuffer(chunk)) throw new Error('Expected a binary request stream')
    bytes += chunk.length
    if (bytes > maxBodyBytes) return respond(res, 413, 'Notification too large')
    chunks.push(chunk)
  }

  let assemblyId: string
  try {
    const form = await new Request('http://localhost/transloadit_pingback', {
      method: 'POST',
      headers: { 'Content-Type': contentType },
      body: Buffer.concat(chunks),
    }).formData()
    const payload = form.get('transloadit')
    const signature = form.get('signature')
    if (
      form.getAll('transloadit').length !== 1 ||
      form.getAll('signature').length !== 1 ||
      typeof payload !== 'string' ||
      typeof signature !== 'string'
    ) {
      return respond(res, 400, 'Expected one payload and one signature')
    }
    if (!(await verifyWebhookSignature({ rawBody: payload, signatureHeader: signature, authSecret: secret }))) {
      return respond(res, 403, 'Invalid signature')
    }
    assemblyId = notificationSchema.parse(JSON.parse(payload)).assembly_id
  } catch {
    return respond(res, 400, 'Invalid notification')
  }

  console.log(`Verified notification for Assembly ${assemblyId}`)
  respond(res, 200, 'Accepted')
}

const server = createServer({ requestTimeout: 30_000 }, (req, res) => {
  receive(req, res, authSecret).catch(() => respond(res, 500, 'Unable to accept notification'))
})
server.listen(port, '127.0.0.1', () => {
  const address = server.address()
  if (address && typeof address !== 'string') {
    console.log(`Server started, listening on http://127.0.0.1:${address.port}`)
  }
})

This verification demo logs only the Assembly ID. It accepts payloads that omit uploads or results, and caps the entire request at 1 MiB. Adjust that limit for your expected payloads or use notification_payload to reduce their size.

Before using this in production, check that the Assembly belongs to the operation and Workspace you expect, validate the fields your application consumes, and durably store or enqueue the verified notification before returning 200. Handle repeated notifications idempotently. A valid signature does not make a notification new, nor does this logging demo persist results. Keep the Auth Secret server-side and put the endpoint behind your HTTPS reverse proxy.

Run the server with the Auth Secret provided through your environment:

$ env AUTH_SECRET=******** node notification-backend-node.ts 3020
Server started, listening on http://127.0.0.1:3020

Trying the Code Example locally

While testing locally behind a NAT, use Cloudflare Tunnel⁠, ngrok⁠, or the first-party @transloadit/notify-url-relay⁠ via npx -y @transloadit/notify-url-relay; unlike tunnels, the relay polls public Assembly Status and forwards terminal notifications to your local notify_url handler.

Recommended (first-party relay), in a new tab:

$ TRANSLOADIT_SECRET=******** npx -y @transloadit/notify-url-relay \
    --notifyUrl "http://127.0.0.1:3020/transloadit_pingback" \
    --log-level info
notify-url-relay [ NOTICE] Listening on http://localhost:8888, forwarding to https://api2.transloadit.com, notifying http://127.0.0.1:3020/transloadit_pingback

When using the relay, point your app/SDK Transloadit endpoint to http://127.0.0.1:8888.

You can now create a Template and paste the following Instructions:

{
  "notify_url": "http://127.0.0.1:3020/transloadit_pingback",
  "steps": {
    ":original": {
      "robot": "/upload/handle"
    },
    "faces_detected": {
      "use": ":original",
      "robot": "/image/facedetect",
      "crop": true,
      "faces": "max-confidence",
      "crop_padding": "10%",
      "format": "preserve"
    }
  }
}

If you prefer a tunnel instead, use Cloudflare Tunnel⁠ or ngrok⁠.

Note

At the time of writing ngrok appears to have issues with AWS ranges⁠. If this is the case for you, an alternative to try is Cloudflare Tunnel⁠ or the first-party @transloadit/notify-url-relay⁠.

Now you're ready to test right inside the browser. The Instructions we used detect a face, so for optimal results, upload a photo of someone using the Template Editor's testing area. You can use Uppy's Webcam feature if you don't have a picture available.

Your Node.js script should report it has successfully received the Assembly Notification when the Assembly completes:

Verified notification for Assembly 0123456789abcdef0123456789abcdef

The Assembly ID in your output will match the Assembly you created.

In addition, you'll see a record of the notification on the Assembly page, where you can also manually retry it for further testing.

The example code higher up shows how to verify webhook signatures in Node.js. For creating Assemblies with automatic signature generation (rather than verifying incoming webhooks), see the SDK examples in the Signature Authentication docs.

Previous page ← TemplatesNext page Third party Credentials →
TransloaditChecking status…

Product

  • Services
  • Pricing
  • Demos
  • Tools
  • Security
  • Support

Company

  • About/Press
  • Blog/Jobs
  • Comparisons/Compliance matrix
  • Research
  • Open source
  • Solutions

Docs

  • Getting started
  • Transcoding
  • FAQ
  • API
  • Guides/DevTips
  • Supported formats

More

  • Platform status⁠
  • Community forum⁠
  • StackOverflow⁠
  • Uppy
  • tus⁠

© 2009–2026 Transloadit-II GmbH

PrivacyTermsImprint
EnglishDeutschEspañolPortuguês (Brasil)