Saltar al contenido
NimbusNexus

Pipelines

Una pipeline es un documento JSON, y esta página describe su lenguaje. Webhooks y Request Bins ejecutan el mismo lenguaje en el mismo motor, así que lo que aprendas aquí también vale allí, dentro de las reglas de cabeceras de cada producto. Para probar todo esto sin cuenta, usa el playground.

El documento

Esta pipeline mantiene las facturas pagadas de $1,000 o más, quita el email del cliente y cualquier número de teléfono, añade dos campos y una cabecera, e indica que un bearer token va en la petición:

JSON
{
  "version": 1,
  "steps": [
    { "filter": { "type": "invoice.paid", "data": { "amount": { "$gte": 100000 } } }, "note": "Paid invoices of $1,000 or more" },
    { "drop": ["data.customer.email", "**.phone"] },
    { "set": { "event_type": "{{ type }}", "amount_display": "{{ data.amount | money(data.currency) }}" } },
    { "headers": { "set": { "X-Event-Type": "{{ type }}" } } },
    { "auth": { "type": "bearer", "secret": "sec_…" } }
  ]
}

version es el formato del documento, siempre 1; no es la versión de la pipeline. Los pasos se ejecutan en el orden en que están escritos. Cada uno es un objeto con una clave de paso y, opcionalmente, una note de hasta 200 caracteres, que la consola muestra en el paso y que un descarte incluye en su respuesta. Un filter también puede llevar una action.

Una ejecución ve una sola petición: un body, cualquier valor JSON; headers; y meta, de solo lectura. En la API de ejecución, los tres son lo que envíes. Cada paso ve el cuerpo tal como lo dejó el anterior. Los pasos se numeran desde 0 en la API y en los textos de error, y desde 1 en el editor de la consola.

PasoEn la consolaQué hace
filterFilterMantiene la petición solo si coincide o, con "action": "drop", la descarta si coincide.
dropRemove fieldsQuita campos del cuerpo.
setSet fieldsAñade o sobrescribe campos.
mapReshape the bodySustituye el cuerpo por uno que construyes tú.
headersHeadersPone, quita o renombra cabeceras.
authCredentialIndica que un bearer token, autenticación básica o una clave de API va en la petición, y nombra el slot del que sale.

filter

Mantiene la petición solo si coincide; si no, la ejecución responde dropped. Con "action": "drop" funciona al revés: lo que coincide se descarta.

JSON
{ "filter": { "data": { "status": "paid", "amount": { "$gte": 100000 } } }, "note": "Paid, $1,000 or more" }
  • Cada clave es una condición, y todas deben cumplirse. Un objeto anidado se compara por su forma, como data aquí.
  • Un valor simple coincide por igualdad JSON: 1 es igual a 1.0.
  • Un array coincide si es igual al valor o si alguno de sus elementos coincide: {"data": {"tags": "urgent"}} se cumple para ["billing", "urgent"], y {"data": {"items": {"sku": "A-1", "qty": 2}}} necesita un elemento que tenga ambos.
  • Un campo que la petición no tiene solo coincide con {"$exists": false} y con las negaciones, como $neq.
  • Los operadores son $eq, $neq, $gt, $gte, $lt, $lte (números con números, texto con texto; las marcas de tiempo ISO se ordenan por tiempo), $in, $nin, $exists, $startsWith, $endsWith, $contains (texto, o un array que contiene el valor), y $and, $or y $not para combinar filtros.
  • $headers, $meta y $body comprueban esa parte de la petición: {"$headers": {"x-github-event": "push"}}.

drop

Quita campos del cuerpo: una lista de 1 a 50 rutas. * equivale a un nivel cualquiera y ** a cualquier profundidad, así que **.email quita todos los campos email, estén donde estén. Una ruta que no coincide con nada no es un error.

JSON
{ "drop": ["data.customer.email", "data.cards[*].number", "**.phone"] }

set

Añade o sobrescribe campos: de 1 a 50 pares de una ruta y un valor. Crea por el camino los objetos que falten, y cada valor lee el cuerpo tal como lo encontró el paso. Una ruta que se topa con un valor que no es un objeto hace fallar la ejecución con set_conflict.

JSON
{ "set": { "event_type": "{{ type }}", "data.total_cents": { "$ref": "data.amount" } } }

map

Sustituye el cuerpo por un valor que escribes tú, normalmente un objeto.

JSON
{ "map": {
    "invoice_id": "{{ data.id }}",
    "amount": { "$ref": "data.amount" },
    "amount_display": "{{ data.amount | money(data.currency) }}",
    "event": "{{ type }}"
} }

Con una petición invoice.paid cuyo data es {"id": "in_42", "amount": 150000, "currency": "usd"}, el cuerpo pasa a ser:

JSON
{ "invoice_id": "in_42", "amount": 150000, "amount_display": "1,500.00 USD", "event": "invoice.paid" }

headers

Pone cabeceras con set, las quita con remove y las renombra con rename, en este orden: remove, rename, set. Los nombres coinciden sin importar mayúsculas y minúsculas, y un nombre que se pone sustituye a todas sus grafías.

JSON
{ "headers": { "set": { "X-Event-Type": "{{ type }}", "Content-Type": "application/json" } } }

Un paso de cabeceras no puede poner una cabecera que pertenezca a la conexión, como Host o Content-Length, ni Set-Cookie. Tampoco puede poner Authorization, Cookie ni X-Api-Key, porque el valor quedaría en el documento, que devuelve cada lectura: una credencial va en un paso auth. Un producto que ejecuta la pipeline por referencia también le aplica sus propias reglas de cabeceras.

auth

Indica que una credencial va en la petición, y nombra el slot del que sale:

typeAñade
bearerAuthorization: Bearer y la credencial.
basicAuthorization: Basic, a partir de un username y de la credencial como contraseña.
api_key, "in": "header"La credencial en la cabecera llamada name, tras un prefix opcional como "Token ".
api_key, "in": "query"La credencial como parámetro de consulta name, en la URL del destino.
JSON
{ "auth": { "type": "api_key", "in": "header", "name": "X-Api-Key", "secret": "sec_…" } }

secret es el id de un slot del workspace. Nunca es un valor: un valor pegado ahí se rechaza con invalid_secret, y el rechazo no lo repite. El producto que ejecuta la pipeline por referencia asigna el slot a un secreto propio (Credenciales). Test y la API de ejecución muestran la cabecera o el parámetro con el valor enmascarado.

Los pasos auth van después de todos los demás, así que ningún paso puede leer, copiar ni comprobar una credencial.

Rutas y valores

Una ruta nombra un campo del cuerpo:

RutaNombra
data.customer.emailUn campo.
data.items[0].skuUn elemento de una lista.
data['first name']Una clave que contiene un espacio, un punto o un corchete.
data.items[*].skuCada elemento; en un valor, la lista de todos ellos.
**.emailemail a cualquier profundidad; solo en un drop.
$meta.source, $headers.x-name, $body.dataLos metadatos de la petición, una de sus cabeceras o su cuerpo; solo en un valor.

set acepta rutas simples, drop acepta * y **, y un valor lee con * pero no con **.

Un valor en set, map o una cabecera es JSON y se copia tal como está escrito, salvo dos cosas:

  • {{ path }} dentro de una cadena inserta un valor como texto. Un valor que falta o null no inserta nada, y un objeto o una lista se insertan como JSON. {{ 'text' }} y {{ 42 }} son literales, así que un {{ literal se escribe {{ '{{' }}.
  • {"$ref": "path"} inserta el valor tal cual y conserva su tipo: un número sigue siendo un número, y una lista, una lista. Añade "default" para cuando falte la ruta.

Una expresión puede pasar su valor por filtros, de izquierda a derecha: {{ data.amount | money(data.currency) }} da 1,500.00 USD para un importe de 150000 en usd.

FiltroDa
upper, lowerEl texto en mayúsculas o en minúsculas.
default(x)x cuando el valor falta, es null o está vacío.
truncate(n)Como máximo n caracteres, seguidos de ….
join(sep)Una lista como texto, unida con ", " salvo que indiques sep.
lengthEl número de elementos, claves o caracteres.
json, json(indent)El valor como JSON compacto, o sangrado con 1 a 8 espacios.
number(digits)Un número con esa cantidad de decimales.
money(currency)Un importe en unidades menores, con formato; incluye las monedas sin decimales y las de tres.
date(format)Una fecha ISO 8601 o segundos epoch, en UTC, con %Y, %m, %d, %H, %M, %S y otros.
escape(target)Texto que no puede dar formato a un mensaje de chat: slack (por defecto), discord o markdown.
code(target, max, language)Texto como bloque de código en el marcado de slack, discord o markdown, que el texto no puede cerrar antes de tiempo, ajustado a max caracteres.

No hay bucles, condiciones ni operaciones aritméticas: una condición es un paso filter, y una lista se convierte en texto con [*] y join.

Plantillas

La galería reúne pipelines ya hechas para empezar. En la consola están en Start from a template. Por la API, GET /v1/pipelines/templates devuelve cada una con su pipeline. Al guardar una, obtienes una pipeline que es tuya: cámbiala como cualquier otra; nada la vincula ya a la plantilla.

idPlantillaQué envía
slackSlack messagePara un incoming webhook de Slack: el tipo del evento como encabezado, su id y su hora, y sus datos en un bloque de código.
discordDiscord messagePara el webhook de un canal de Discord: un embed con el tipo del evento, sus datos en un bloque de código, y su id y su hora. No menciona a nadie.
teamsMicrosoft Teams cardPara el webhook de un flujo de trabajo de Teams: una Adaptive Card con el tipo del evento, su id y su hora, y sus datos en monoespaciado.
keep_fieldsKeep only these fieldsEl id, type, created_at y version del evento, y solo los campos de data que lista su map, empezando por data.id. Un campo de la lista que el evento no tiene se envía como null.
drop_personalDrop personal dataEl evento sin email, phone, first_name, last_name, address, billing_address, shipping_address, date_of_birth ni ip_address, a cualquier profundidad. Un name a secas se queda, porque a menudo nombra un plan o un producto.

Cada plantilla de chat es un paso map y un paso headers que pone Content-Type: application/json, y funciona con cualquier evento. Un tipo que falta aparece como "Event" y un id o una hora que faltan, como "—"; los datos se recortan para caber en los límites de la app, y nada del evento puede dar formato al mensaje, mencionar a nadie ni cerrar antes de tiempo el bloque de código.

Límites

Una pipeline no tiene bucles, ni reloj, ni red, y unos límites fijos acotan cada ejecución. Al superar uno, la ejecución responde failed con el motivo, y un documento que supere uno se rechaza al guardarlo.

QuéLímiteSe rechaza como
Un documento, como JSON compacto64 KiBtoo_large
Pasos20too_many_steps
Rutas en un drop, campos en un set, nombres en cada parte de un paso headers50invalid_value
La configuración de un paso, como JSON compacto16 KiBtoo_large
La nota de un paso200 caracteresinvalid_value
Anidamiento, en un paso y en un cuerpo32 nivelestoo_deep, input_too_deep
El cuerpo que lee un paso256 KiBinput_too_large
El cuerpo con el que responde una ejecución256 KiBoutput_too_large
Un valor que genera un paso64 KiBvalue_too_large
Trabajo en una ejecución100,000 valores leídos o copiados; el texto cuenta uno por cada 128 caracterestoo_many_nodes
Cabeceras tras cada paso50, o tantas como traiga la petición si son mástoo_many_headers
Un valor de cabecera8 KiBheader_too_large en una ejecución, para un valor con una expresión; invalid_header al guardar, para uno sin ella

El playground tiene límites más bajos: un documento de 16 KiB, un cuerpo de 32 KiB, 16 KiB para un valor que genera un paso, 20 cabeceras, 1 KiB para un valor de cabecera y 10,000 valores leídos o copiados en una ejecución.

Próximos pasos