> ## Documentation Index
> Fetch the complete documentation index at: https://docs.onepay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Enviar campaña masiva de WhatsApp

> Envía una campaña de plantilla de WhatsApp (con header de imagen o video) a una lista de números telefónicos.

Encola una campaña de plantilla de WhatsApp hacia una lista de números telefónicos. Un único parámetro `type` discrimina si la campaña usa un **header de imagen** o de **video**.

El procesamiento es **asíncrono**: el endpoint responde apenas encola los envíos (un job en background por cada teléfono, con rate-limit por canal de WhatsApp). Un `200` significa **"encolado"**, no "entregado".

<Warning>
  **Migración:** Este endpoint **reemplaza** al antiguo `POST /campaigns/import-video-messages`, que **fue eliminado**. Los consumidores de video deben migrar a `/campaigns/import-messages` enviando `type: "video"`. Los consumidores de imagen que **no** envían `type` no se ven afectados: el valor por defecto sigue siendo `image` y la respuesta conserva la clave `image_path`.
</Warning>

<Warning>
  **Feature flag requerido:** La empresa debe tener habilitada la feature `campaigns`. Si no está habilitada, el endpoint responde `403 Forbidden` con el mensaje "Company does not have this feature."
</Warning>

### Headers

<ParamField header="Content-Type" type="string" required placeholder="application/json">
  Debe ser `application/json`.
</ParamField>

<ParamField header="Accept" type="string" required placeholder="application/json">
  Debe ser `application/json`.
</ParamField>

### Body

<ParamField body="type" type="string" default="image">
  Tipo de campaña. Valores permitidos: `image`, `video`. Si se omite, el valor por defecto es `image`.
</ParamField>

<ParamField body="template" type="string" required placeholder="sensibilizacion_primera_comunicacion">
  Nombre de la plantilla de WhatsApp **aprobada en Meta/Dialog360** con el header correspondiente al `type`. Debe pertenecer a la lista permitida según el tipo:

  * **`type: "image"`** → `sensibilizacion_primera_comunicacion`, `sensibilizacion_primera_comunicacion_v2`.
  * **`type: "video"`** → configurables del lado del servidor (actualmente `sensibilizacion_primera_comunicacion_video`, `recordatorio_pago_video_v1`, `promo_video_r1`, `pago_factura_video_push1`).

  <Note>
    La plantilla debe estar aprobada en Meta/Dialog360 con el header del tipo correcto (`IMAGE` o `VIDEO`). Un nombre no aprobado falla en Meta aunque el endpoint responda `200`.
  </Note>
</ParamField>

<ParamField body="image_url" type="string" placeholder="https://cdn.tu-dominio.com/banner.png">
  URL pública de la imagen para el header del template. **Requerido cuando `type` es `image`** (o cuando se omite). Se ignora si `type` es `video`. El backend descarga la imagen para validarla, por lo que el host debe ser accesible públicamente.
</ParamField>

<ParamField body="video_url" type="string" placeholder="https://cdn.tu-dominio.com/clip.mp4">
  URL pública del MP4 para el header del template. **Requerido cuando `type` es `video`.** Se ignora en cualquier otro caso. El backend descarga el video para validarlo, por lo que el host debe responder al `GET`.
</ParamField>

<ParamField body="optional_1" type="string">
  Variable 1 del cuerpo de la plantilla. Para el template `sensibilizacion_primera_comunicacion`, corresponde al nombre de la compañía que envía la campaña.
</ParamField>

<ParamField body="optional_2" type="string">
  Variable 2 del cuerpo de la plantilla.
</ParamField>

<ParamField body="optional_3" type="string">
  Variable 3 del cuerpo de la plantilla.
</ParamField>

<ParamField body="phones" type="string[]" required>
  Lista de números telefónicos. Mínimo 1 número. Cada uno debe ser un teléfono válido (formato E.164 recomendado, ej: `+573001112233`) de máximo 20 caracteres.
</ParamField>

<Note>
  Los valores `optional_1`, `optional_2` y `optional_3` se envían como los parámetros del **cuerpo** de la plantilla, en orden. Los vacíos se descartan.
</Note>

### Requisitos del archivo multimedia

Los límites siguen las restricciones de la WhatsApp Cloud API. El backend descarga y valida el archivo antes de encolar los envíos.

<Tabs>
  <Tab title="Imagen (type: image)">
    * **Peso máximo:** 2 MB.
    * **Formatos permitidos:** `JPEG` o `PNG`.
    * **Dimensiones:** lado más corto **≥ 600 px**, lado más largo **≤ 2048 px**.
    * **Relación de aspecto:** `4:3`, `9:16` o `2:3` (tolerancia ±1%).
    * El host debe ser accesible: el backend descarga la imagen para validarla.
  </Tab>

  <Tab title="Video (type: video)">
    * **Peso máximo:** 16 MB.
    * **Formato:** `video/mp4` únicamente (códec H.264 + AAC recomendado; **`3gp` no se acepta**).
    * El host debe responder al `GET`; idealmente enviar `Content-Length` (se usa para el pre-corte rápido de tamaño).
    * Debe ser un MP4 válido (se verifica el box `ftyp`).
  </Tab>
</Tabs>

<RequestExample>
  ```bash cURL - Campaña de video theme={null}
  curl https://api.onepay.la/v1/campaigns/import-messages \
    -X POST \
    -H "Authorization: Bearer sk_test_xxx" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "type": "video",
      "template": "sensibilizacion_primera_comunicacion_video",
      "video_url": "https://cdn.tu-dominio.com/clip.mp4",
      "optional_1": "Maria",
      "phones": [
        "+573001112233",
        "+573009998877"
      ]
    }'
  ```

  ```bash cURL - Campaña de imagen theme={null}
  curl https://api.onepay.la/v1/campaigns/import-messages \
    -X POST \
    -H "Authorization: Bearer sk_test_xxx" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "template": "sensibilizacion_primera_comunicacion",
      "image_url": "https://cdn.tu-dominio.com/banner.png",
      "phones": [
        "+573001112233"
      ]
    }'
  ```

  ```javascript JavaScript - Campaña de video theme={null}
  const response = await fetch('https://api.onepay.la/v1/campaigns/import-messages', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_test_xxx',
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: JSON.stringify({
      type: 'video',
      template: 'sensibilizacion_primera_comunicacion_video',
      video_url: 'https://cdn.tu-dominio.com/clip.mp4',
      optional_1: 'Maria',
      phones: [
        '+573001112233',
        '+573009998877'
      ]
    })
  });

  const result = await response.json();
  console.log(result.queued); // Jobs encolados (uno por teléfono)
  ```

  ```javascript JavaScript - Campaña de imagen theme={null}
  const response = await fetch('https://api.onepay.la/v1/campaigns/import-messages', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer sk_test_xxx',
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    },
    body: JSON.stringify({
      template: 'sensibilizacion_primera_comunicacion',
      image_url: 'https://cdn.tu-dominio.com/banner.png',
      phones: [
        '+573001112233'
      ]
    })
  });

  const result = await response.json();
  console.log(result.queued); // Jobs encolados (uno por teléfono)
  ```

  ```python Python - Campaña de video theme={null}
  import requests

  url = "https://api.onepay.la/v1/campaigns/import-messages"
  headers = {
      "Authorization": "Bearer sk_test_xxx",
      "Content-Type": "application/json",
      "Accept": "application/json"
  }
  data = {
      "type": "video",
      "template": "sensibilizacion_primera_comunicacion_video",
      "video_url": "https://cdn.tu-dominio.com/clip.mp4",
      "optional_1": "Maria",
      "phones": [
          "+573001112233",
          "+573009998877"
      ]
  }

  response = requests.post(url, headers=headers, json=data)
  result = response.json()
  print(result["queued"])  # Jobs encolados (uno por teléfono)
  ```

  ```python Python - Campaña de imagen theme={null}
  import requests

  url = "https://api.onepay.la/v1/campaigns/import-messages"
  headers = {
      "Authorization": "Bearer sk_test_xxx",
      "Content-Type": "application/json",
      "Accept": "application/json"
  }
  data = {
      "template": "sensibilizacion_primera_comunicacion",
      "image_url": "https://cdn.tu-dominio.com/banner.png",
      "phones": [
          "+573001112233"
      ]
  }

  response = requests.post(url, headers=headers, json=data)
  result = response.json()
  print(result["queued"])  # Jobs encolados (uno por teléfono)
  ```

  ```php PHP - Campaña de video theme={null}
  <?php
  $ch = curl_init();

  curl_setopt_array($ch, [
      CURLOPT_URL => "https://api.onepay.la/v1/campaigns/import-messages",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer sk_test_xxx",
          "Content-Type: application/json",
          "Accept: application/json"
      ],
      CURLOPT_POSTFIELDS => json_encode([
          "type" => "video",
          "template" => "sensibilizacion_primera_comunicacion_video",
          "video_url" => "https://cdn.tu-dominio.com/clip.mp4",
          "optional_1" => "Maria",
          "phones" => [
              "+573001112233",
              "+573009998877"
          ]
      ])
  ]);

  $response = curl_exec($ch);
  $result = json_decode($response, true);

  echo $result["queued"]; // Jobs encolados (uno por teléfono)

  curl_close($ch);
  ?>
  ```

  ```php PHP - Campaña de imagen theme={null}
  <?php
  $ch = curl_init();

  curl_setopt_array($ch, [
      CURLOPT_URL => "https://api.onepay.la/v1/campaigns/import-messages",
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
          "Authorization: Bearer sk_test_xxx",
          "Content-Type: application/json",
          "Accept: application/json"
      ],
      CURLOPT_POSTFIELDS => json_encode([
          "template" => "sensibilizacion_primera_comunicacion",
          "image_url" => "https://cdn.tu-dominio.com/banner.png",
          "phones" => [
              "+573001112233"
          ]
      ])
  ]);

  $response = curl_exec($ch);
  $result = json_decode($response, true);

  echo $result["queued"]; // Jobs encolados (uno por teléfono)

  curl_close($ch);
  ?>
  ```
</RequestExample>

### Response

<ResponseField name="message" type="string">
  Mensaje de confirmación. Valor: `Campaign queued`.
</ResponseField>

<ResponseField name="type" type="string">
  Tipo de campaña procesada (`image` o `video`).
</ResponseField>

<ResponseField name="template" type="string">
  Nombre de la plantilla utilizada.
</ResponseField>

<ResponseField name="queued" type="integer">
  Cantidad de jobs encolados (uno por cada teléfono de `phones`).
</ResponseField>

<ResponseField name="image_path" type="string">
  Ruta de almacenamiento de la imagen descargada. Presente cuando `type` es `image`.
</ResponseField>

<ResponseField name="video_path" type="string">
  Ruta de almacenamiento del video descargado. Presente cuando `type` es `video`.
</ResponseField>

<ResponseField name="date" type="string">
  Fecha y hora de encolamiento, en formato `YYYY-MM-DD HH:mm:ss`.
</ResponseField>

<ResponseExample>
  ```json 200 - Video theme={null}
  {
    "message": "Campaign queued",
    "type": "video",
    "template": "sensibilizacion_primera_comunicacion_video",
    "queued": 2,
    "video_path": "companies/42/campaigns/sensibilizacion/2026-06-24/9f1c2b....mp4",
    "date": "2026-06-24 14:05:11"
  }
  ```

  ```json 200 - Imagen theme={null}
  {
    "message": "Campaign queued",
    "type": "image",
    "template": "sensibilizacion_primera_comunicacion",
    "queued": 1,
    "image_path": "companies/42/campaigns/sensibilizacion/2026-06-24/3a7d9f....png",
    "date": "2026-06-24 14:05:11"
  }
  ```

  ```json 403 theme={null}
  {
    "message": "Company does not have this feature."
  }
  ```

  ```json 422 - Imagen theme={null}
  {
    "message": "La imagen no debe superar los 2 MB.",
    "code": 10001,
    "code_name": "validation_error",
    "errors": {
      "image_url": [
        "La imagen no debe superar los 2 MB."
      ]
    }
  }
  ```

  ```json 422 - Video theme={null}
  {
    "message": "El video no debe superar los 16 MB.",
    "code": 10001,
    "code_name": "validation_error",
    "errors": {
      "video_url": [
        "El video no debe superar los 16 MB."
      ]
    }
  }
  ```

  ```json 422 - Phones theme={null}
  {
    "message": "El campo phones es obligatorio. (y 1 error más)",
    "code": 10001,
    "code_name": "validation_error",
    "errors": {
      "template": [
        "El template no es válido."
      ],
      "phones": [
        "El campo phones es obligatorio."
      ]
    }
  }
  ```
</ResponseExample>
