# Webhooks

En lugar de esperar a que finalice una Assembly y responda su solicitud a la API, también puedes configurar un webhook, también conocido como Assembly Notification. El sistema enviará una solicitud POST a una URL que elijas con un informe completo una vez que finalice una Assembly.

## ¿Por qué usar webhooks?

Al optar por usar webhooks, permites que tus usuarios finales disfruten de una experiencia más fluida, ya que solo necesitan esperar a que finalicen las subidas de archivos antes de cerrar la ventana del navegador. Si no hay subidas de archivos, incluso podrían cerrar la ventana del navegador de inmediato.

## ¿Cómo activar los webhooks?

Para activar los webhooks, agrega `notify_url` a las Assembly Instructions de tuTemplate, en el mismo nivel del JSON que `steps`:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

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

```

Cuando ejecutes tu Template, Transloadit informará a tu back-end una vez que haya finalizado todo el procesamiento mediante el envío de una solicitud `POST` a la URL definida, con todo el Assembly Status JSON.

Si no quieres que tus usuarios o tu programa esperen a que finalice el encoding, normalmente también tendrás que configurar un indicador. En el caso de [Uppy](/docs/sdks/uppy.md), establece el parámetro `waitForEncoding` en `false`. En muchos SDK de back-end, esperar a que finalice el encoding implica consultar explícitamente el Assembly Status, por lo que bastará con no hacerlo.

Tu back-end debe responder con un encabezado `200`; de lo contrario, Transloadit asumirá que la Notification falló y reintentará el envío varias veces con espera exponencial.

## Personalizar el contenido de la Notification

De forma predeterminada, las solicitudes de webhook incluyen elAssembly Status JSON completo. Si tus Assemblies generan muchos resultados, puedes reducir el tamaño del contenido con `notification_payload`.

Agrégalo junto a `notify_url`:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

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

```

Valores compatibles:

* `without_results`: omitir el objeto `results` de nivel superior del contenido del webhook
* `without_result_meta_data`: conservar `results`, pero eliminar `meta` de cada archivo resultante
* `without_uploads`: omitir el arreglo `uploads` de nivel superior del contenido del webhook
* `without_upload_meta_data`: conservar `uploads`, pero eliminar `meta` de cada archivo subido

Puedes combinar varios valores en el mismo arreglo. Esto también se aplica cuando vuelves a enviar Notifications desde la página de la Assembly.

## ¿Cómo es esta solicitud POST?

Esta solicitud POST multipart contiene un campo llamado `transloadit`, que incluye elAssembly Status JSON completo. Puedes consultar un ejemplo en nuestra[documentación de respuestas de la API](/docs/api/assemblies-assembly-id-get.md#response). También incluirá un campo`signature` para que puedas verificar, de manera opcional, que la solicitud realmente provino de nosotros y no fue alterada. El siguiente ejemplo de código muestra cómo calcular esa firma con tu Auth Secret para que coincida con la que enviamos.

## Ejemplo de código

Supongamos que efectivamente especificaste `"notify_url": "https://example.com/transloadit_pingback"` y que el servidor back-end que aceptaría allí las solicitudes POST entrantes está escrito en Node.js.

###### Nota

Este ejemplo muestra cómo **verificar** las firmas de webhooks entrantes de Transloadit, lo cual es diferente de **generar** firmas para solicitudes a la API. Para crear Assemblies con generación automática de firmas, usa nuestros [SDK](/docs/sdks.md). Si quieres ver cómo funciona internamente la verificación de firmas, puedes consultar el[código fuente del SDK de Node](https://github.com/transloadit/node-sdk/blob/main/src/Transloadit.ts).

El servidor de verificación podría verse así:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

```js
import crypto from 'node:crypto'
import http from 'node:http'

import formidable from 'formidable'

const PORT = process.argv[2] || 3020

if (!/^[A-Za-z0-9]{40}$/.test(process.env.AUTH_SECRET)) {
  throw new Error(`Please pass the secret from https://transloadit.com/c/template-credentials
    via the AUTH_SECRET environment var. It must be the auth secret that belongs to the auth key you used for the original Assembly.`)
}

const checkSignature = (fields, authSecret) => {
  const receivedSignature = fields.signature
  const payload = fields.transloadit

  if (!receivedSignature || !payload) {
    return false
  }

  // If the signature contains a colon, we expect it to be of format `algo:actual_signature`.
  // If there are no colons, we assume it's a legacy signature using SHA-1.
  const algoSeparatorIndex = receivedSignature.indexOf(':')
  const algo = algoSeparatorIndex === -1 ? 'sha1' : receivedSignature.slice(0, algoSeparatorIndex)

  try {
    const calculatedSignature = crypto
      .createHmac(algo, authSecret)
      .update(Buffer.from(payload, 'utf-8'))
      .digest('hex')

    // If we are in legacy signature mode, algoSeparatorIndex is -1 and we are
    // comparing the whole string. Otherwise we slice out the prefixed algo.
    return calculatedSignature === receivedSignature.slice(algoSeparatorIndex + 1)
  } catch {
    // We can assume the signature string was ill-formed.
    return false
  }
}

const respond = (res, code, messages) => {
  if (code !== 200) {
    console.error({ messages, code })
  }

  res.writeHead(code, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'OPTIONS, POST, GET',
  })

  if (messages) {
    res.write(JSON.stringify({ messages }))
  }

  res.end()
}

http
  .createServer((req, res) => {
    if (req.method === 'OPTIONS') {
      return respond(res, 204)
    }
    if (req.url === '/transloadit_pingback' && req.method === 'POST') {
      const form = new formidable.IncomingForm()
      form.parse(req, (err, fields) => {
        if (err) {
          return respond(res, 500, [`Error while parsing multipart form`, err])
        }

        if (!checkSignature(fields, process.env.AUTH_SECRET)) {
          return respond(res, 403, [
            `Error while checking signatures`,
            `No match so payload was tampered with, or an invalid Auth Secret was used`,
          ])
        }

        let assembly = {}
        try {
          assembly = JSON.parse(fields.transloadit)
        } catch (err) {
          return respond(res, 500, [`Error while parsing transloadit field`, err])
        }

        console.log(`--> ${assembly.ok || assembly.error} ${assembly.assembly_ssl_url}`)

        for (const upload of assembly.uploads) {
          // save upload.ssl_url and metadata to your db here
          console.log(`    ^- uploaded '${upload.name}' ready at ${upload.ssl_url}`)
        }

        for (const stepName in assembly.results) {
          for (const result of assembly.results[stepName]) {
            // save result.ssl_url and metadata to your db here
            console.log(`    ^- ${stepName} '${result.name}' ready at ${result.ssl_url}`)
          }
        }

        return respond(res, 200, [`Success!`])
      })
    } else {
      return respond(res, 500, [
        `Welcome! I only know how to handle POSTs to /transloadit_pingback`,
        `No handler for req.url=${req.url}, req.method=${req.method}`,
      ])
    }
  })
  .listen(PORT, () => {
    console.log(`Server started, listening on http://0.0.0.0:${PORT}`)
  })

```

Podrías ejecutar este script de la siguiente manera:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

```console
$ env AUTH_SECRET=******** node notification-backend-node.js 3020
Server started, listening on http://0.0.0.0:3020

```

## Probar el ejemplo de código localmente

Cuando hagas pruebas locales detrás de una NAT, usa [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/), [ngrok](https://ngrok.com/) o la herramienta oficial [@transloadit/notify-url-relay](https://www.npmjs.com/package/@transloadit/notify-url-relay) mediante `npx -y @transloadit/notify-url-relay`; a diferencia de los túneles, el relay consulta el Assembly Status público y reenvía las notificaciones finales a tu controlador local de `notify_url`.

Opción recomendada (relay oficial), en una pestaña nueva:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

```console
$ 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

```

Cuando uses el relay, configura el endpoint de Transloadit de tu aplicación o SDK como `http://127.0.0.1:8888`.

Ahora puedes [crear un Template](/es/docs/topics/templates.md#how-to-create-a-template) y pegar las siguientesInstructions:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

```json
{
  "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"
    }
  }
}

```

Si prefieres usar un túnel, utiliza [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/) o [ngrok](https://ngrok.com/).

###### Nota

En el momento de redactar esta documentación,[ngrok parece tener problemas con los rangos de AWS](https://github.com/inconshreveable/ngrok/issues/408#issuecomment-791161309). Si este es tu caso, puedes probar como alternativa[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/)o la herramienta oficial [@transloadit/notify-url-relay](https://www.npmjs.com/package/@transloadit/notify-url-relay).

Ahora ya puedes hacer la prueba directamente en el navegador. El conjunto de Instructions que usamos detecta un rostro; para obtener resultados óptimos, sube una foto de una persona mediante el área de pruebas del Template Editor. Puedes usar la función Webcam de Uppy si no tienes una imagen disponible.

Tu script de Node.js debería indicar que recibió correctamente la Assembly Notification cuando finalice la Assembly:

![](/_next/static/media/copy.04p1cju9qekk_.svg?dpl=dpl_6ZskK4ZaqZ1GYVxrmc4Qz2KncJJA)

```plaintext
-- > ASSEMBLY_COMPLETED https://api2.transloadit.com/assemblies/b2b580bdc969427091a48f1f0d3d9d40
^- uploaded 'avatar.jpg' ready at https://s3.amazonaws.com/tmp.transloadit.com/ff89be82...
^- faces_detected 'avatar.jpg' ready at https://s3.amazonaws.com/tmp.transloadit.com/fd2f61b9...

```

Además, verás un registro de la Notification en la página de la Assembly, donde también podrás reintentar manualmente su envío para realizar más pruebas.

El código de ejemplo anterior muestra cómo verificar firmas de webhooks en Node.js. Para crear Assemblies con generación automática de firmas, en lugar de verificar webhooks entrantes, consulta los ejemplos de los SDK en la [documentación de Signature Authentication](/docs/api/authentication.md).
