Mover un lead de etapa en un tablero

Cuando algo pasa en tu sistema, como un pago o una demo agendada, mové al contacto a la etapa que corresponde y enterate de cada cambio de etapa.

Vas a reflejar en un tablero de Funnelchat lo que pasa en tu sistema: un pago recibido, una demo agendada, un lead descartado. Usa Listar tableros para conocer los tableros y sus etapas, Ver las etapas de un contacto en los tableros para saber dónde está cada contacto y Mover un contacto de etapa para moverlo. Con el evento contact.stage_changed te enterás de cada cambio de etapa en un tablero por etapas.

Al terminar esta guía, tu sistema mueve contactos entre etapas de un tablero con una llamada y recibe un aviso firmado cada vez que un contacto cambia de etapa.

Antes de empezar

  • Una clave con conversations:write y conversations:read. Escribir, para mover contactos; leer, para los tableros y las etapas, y como permiso del área del evento del paso 4. Para el paso 4 necesitás además webhooks:write, que administra la suscripción. Si todavía no tenés una, seguí la guía rápida.
  • La clave en la variable FC_API_KEY, entre comillas si la guardás en un .env (lleva un |).
  • El id del contacto. Si partís de un teléfono, usá Crear o actualizar un contacto, que devuelve el contacto con su id (Sincronizar contactos con tu CRM).
  • Para el paso 4, un receptor de webhooks con URL https pública, como el de Recibir los mensajes de WhatsApp en tu sistema.

1. Encontrá el tablero y sus etapas

Listar tableros devuelve tus tableros, cada uno con sus etapas (stages). Anotá el id del tablero y el id de cada etapa que vas a usar.

curl -G "https://api.funnelchat.com/api/v1/kanban-settings" \
  --data-urlencode "perPage=100" \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Accept: application/json"

Respuesta de ejemplo (recortada a un tablero, a algunos de sus campos y sin links ni meta):

{
  "data": [
    {
      "id": 5001,
      "name": "Embudo de ejemplo",
      "is_primary": true,
      "stagnant_days": null,
      "stages": [
        { "id": 6001, "kanban_setting_id": 5001, "name": "Nuevo", "position": 0, "contacts_count": 12 },
        { "id": 6002, "kanban_setting_id": 5001, "name": "En seguimiento", "position": 1, "contacts_count": 5 },
        { "id": 6003, "kanban_setting_id": 5001, "name": "Ganado", "position": 2, "contacts_count": 3 }
      ],
      "created_at": "2026-09-01T10:00:00.000000Z",
      "updated_at": "2026-09-01T10:00:00.000000Z"
    }
  ]
}

Un tablero se arma por etapas o por etiquetas, y lo dice mode. Usá uno por etapas: el evento contact.stage_changed se envía en tableros por etapas, y la especificación no documenta qué hace mover un contacto en un tablero con mode tags, que se arma con etiquetas.

2. Mirá en qué etapa está el contacto

Ver las etapas de un contacto en los tableros devuelve, por cada tablero, la etapa actual del contacto (current_stage_id). Sirve para no mover a un contacto que ya está donde corresponde y para mostrar su avance en tu sistema.

curl -G "https://api.funnelchat.com/api/v1/contacts/102578466/kanban-stages" \
  --data-urlencode "perPage=100" \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Accept: application/json"

Respuesta de ejemplo (recortada a algunos campos y sin links ni meta):

{
  "data": [
    {
      "kanban_setting_id": "5001",
      "kanban_name": "Embudo de ejemplo",
      "current_stage_id": "6001"
    }
  ]
}

La especificación declara kanban_setting_id y current_stage_id como texto, mientras que los id de los tableros y las etapas son enteros: convertilos antes de compararlos, como en el código.

3. Mové el contacto

Mover un contacto de etapa lleva el tablero en la ruta y, en el cuerpo, contact_id y stage_id (los dos obligatorios). Pide conversations:write.

curl -X POST "https://api.funnelchat.com/api/v1/kanban-settings/5001/move-contact" \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{ "contact_id": 102578466, "stage_id": 6002 }'

Responde 200; la especificación no documenta un cuerpo en la respuesta. Para confirmar el movimiento, volvé a consultar la etapa del contacto (paso 2) o Listar los contactos de una etapa.

Para muchos contactos a la vez, Mover contactos de etapa en bloque recibe contact_ids (de 1 a 200) y stage_id. Si tu cuenta está en bloqueo por el límite de contactos activos (MAC), mueve solo los contactos que no son inactivos: los inactivos se quedan donde estaban, sin error. Si dejó afuera a alguno, responde 200 con { "excluded_inactive": <cantidad> }; si no, el cuerpo es null. Para sacar a un contacto del tablero, por ejemplo si el lead se perdió, usá Sacar un contacto del tablero.

4. Enterate de cada cambio de etapa

Para que tu sistema sepa cuándo un contacto cambia de etapa, suscribite a contact.stage_changed con Crear un webhook, el mismo lugar para los eventos de las tres áreas. Pide webhooks:write y, por ser un evento de conversations, conversations:read.

curl -X POST "https://api.funnelchat.com/api/v1/webhooks" \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tu-servidor.example/hooks/funnelchat",
    "description": "Cambios de etapa",
    "events": ["contact.stage_changed"]
  }'

Si ya tenés una suscripción, por ejemplo la de message.received, podés sumarle el evento con Actualizar un webhook en lugar de crear otra: PUT reemplaza la lista, así que mandá url y todos los events, los que ya tenía y el nuevo. Cada entrega llega firmada: verificala como en Verificar la firma.

Payload de ejemplo (del catálogo de eventos):

{
  "id": "evt_000000000000000000000008",
  "type": "contact.stage_changed",
  "version": 1,
  "occurred_at": "2026-10-01T12:00:00Z",
  "data": {
    "contact": {
      "id": 2001,
      "name": "Contacto de ejemplo",
      "phone": "+5491100000000"
    },
    "board": {
      "id": 5001,
      "name": "Embudo de ejemplo"
    },
    "kind": "move",
    "from_stage": {
      "id": 6001,
      "name": "Nuevo",
      "position": 0
    },
    "to_stage": {
      "id": 6002,
      "name": "En seguimiento",
      "position": 1
    }
  }
}

kind dice qué pasó:

kindQué pasó
entryEl contacto entró al tablero. from_stage es null.
movePasó de una etapa a otra.
exitSalió del tablero. to_stage es null.
stage_deletedSe reubicó porque su etapa se borró; el nombre de la etapa borrada es null.
  • Llega un evento por cada contacto que cambia de etapa en un tablero por etapas. No llega si el contacto se coloca en la etapa en la que ya estaba.
  • El payload no dice quién movió al contacto. Si tu sistema mueve y escucha a la vez, preparalo para recibir también el aviso de sus propios movimientos y no tratarlo como un cambio nuevo.
  • Descartá los repetidos por el id del evento, como en cualquier webhook (Qué hacer en tu receptor).

Si falla

Qué vesQué pasóQué hacer
401Falta la clave, no existe o fue revocada.Revisá la cabecera Authorization: Bearer con la clave completa.
403 con error: "permission_denied"A la clave le falta el permiso: conversations:write para mover, conversations:read para leer tableros y etapas, webhooks:write para la suscripción.Creá una clave con ese permiso. message dice cuál falta.
403 sin errorLo rechazó la autorización propia de conversations, no el portero.Leé message.
404Algo de lo que pediste no existe en la cuenta, por ejemplo el tablero de la ruta o el contacto.Revisá los id con los pasos 1 y 2.
422La validación falló: falta contact_id o stage_id, o un valor no es válido.errors dice qué campo corregir. Revisá que la etapa sea del tablero de la ruta.
No llega contact.stage_changedEl contacto ya estaba en esa etapa, el tablero no es por etapas o la suscripción está pausada o deshabilitada.Revisá la etapa actual (paso 2), el mode del tablero y status y disabled_reason de la suscripción.
503El portero no pudo evaluar la llamada (gate_unavailable).Esperá lo que indica Retry-After y reintentá.

El detalle de los códigos está en Errores.

Relacionado

En esta página