Saltar al contenido principal

Contactos y consentimiento

Todo número que conversa con tu cuenta se vuelve un contacto. La API permite que tu sistema — un CRM, una tienda, un ERP — mantenga esa base sincronizada: crear y actualizar contactos, guardar campos propios, organizarlos con etiquetas y, sobre todo, registrar quién aceptó recibir marketing. Ese registro es lo que decide quién entra en una campaña.

Crear o actualizar un contacto

curl -X POST 'https://api.joinotify.com/contacts?upsert=true' \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{
"phone": "+55 41 98711-1527",
"firstName": "Ana",
"email": "[email protected]",
"attributes": { "plan": "Pro" }
}'

El teléfono se normaliza a E.164 y se guarda sin el + (5541987111527). Sin código de país, envía defaultCountry: "BR". Los móviles brasileños con y sin el noveno dígito se reconocen como el mismo contacto.

Sin ?upsert=true, un teléfono que ya existe responde 409 contact_exists con su contactId. Con él, el contacto existente se actualiza y la respuesta es 200.

Sincronizar por lotes

Para una base grande, POST /contacts/batch acepta hasta 500 contactos por llamada y devuelve el resultado de cada fila, en el orden en que se enviaron:

{
"contacts": [
{ "phone": "+5541987111527", "name": "Ana Souza", "tags": ["cliente"] },
{ "phone": "+5511912345678", "firstName": "Bruno", "attributes": { "ciudad": "São Paulo" } }
],
"options": { "onDuplicate": "update", "defaultCountry": "BR" }
}

La respuesta es 200 aunque fallen filas — revisa results[].status. Las filas con failed traen reason y, cuando se puede, el field culpable. Un teléfono repetido dentro del mismo lote se vuelve un solo contacto.

En una actualización por lote, solo los valores rellenos sobrescriben y las etiquetas se suman, nunca se quitan. Para una importación de miles de filas en varios lotes, abre antes un registro con POST /contacts/imports y envía su id en options.importId: los contadores se suman allí, y el panel muestra el avance.

Un lote no dispara webhooks

contact.created y contact.opted_in salen en la creación individual, no en el lote — una importación de diez mil filas no se vuelve diez mil llamadas a tu endpoint.

Campos personalizados y etiquetas

Los campos personalizados guardan lo tuyo: el plan contratado, la ciudad, la fecha de la última compra. Crea el campo una vez con POST /contacts/fields y graba el valor en attributes, por su key. El valor se valida por tipo — un campo date acepta 2026-09-19 o 19/09/2026, un boolean acepta si/no, un select solo acepta las opciones registradas.

Las etiquetas son rótulos libres para segmentar. En una edición individual, tagIds es el conjunto completo; para poner o quitar una etiqueta a muchos contactos, usa POST /contacts/tags/apply con una lista de ids o con los mismos filtros del listado.

Consentimiento de marketing

Las campañas de marketing solo van a quien aceptó recibirlas. Cada contacto tiene optInStatus: opted_in, opted_out o unknown.

Registra la aceptación junto con la evidencia de cómo se obtuvo — es lo que vas a necesitar mostrar si alguien pregunta:

curl -X POST https://api.joinotify.com/contacts/cm1c0ntact0001/opt-in \
-H 'Authorization: Bearer sk_live_xxx' \
-H 'Content-Type: application/json' \
-d '{ "evidence": "Casilla marcada en el checkout del pedido 1042" }'

O ya en la creación, con "optIn": { "evidence": "..." }.

La baja ocurre por varios caminos, y no todos pasan por tu integración: la persona envía una palabra de baja, Meta rechaza un envío con el error 131050, alguien la registra en el panel o tu integración llama a POST /contacts/{id}/opt-out. Para enterarte de todas, suscríbete al evento contact.opted_out.

Una baja no se deshace sola

Ningún camino automático — lote, upsert, unión de duplicados — convierte una baja en un opt-in. Solo un nuevo POST /contacts/{id}/opt-in, con evidencia, lo hace.

Lista de supresión

La lista de supresión es lo que garantiza la baja: los teléfonos y BSUID que están en ella nunca reciben una campaña de marketing, sea cual sea el consentimiento del contacto. Una baja pone el contacto en la lista automáticamente, con el motivo opt_out; puedes añadir otros a mano con POST /suppressions.

MotivoDe dónde viene
opt_outEl contacto se dio de baja
meta_marketing_blockMeta está bloqueando el marketing para ese número
invalid_numberEl número no tiene WhatsApp
manualAlguien lo añadió en el panel o con POST /suppressions

Quitar de la lista una fila opt_out o meta_marketing_block exige confirm=true — y no cambia el consentimiento: el contacto sigue dado de baja hasta un nuevo opt-in.

Privacidad

  • DELETE /contacts/{id} borra el contacto de forma definitiva, con conversaciones e historial — la solicitud de supresión de datos. La fila de la lista de supresión queda, sin la identidad.
  • GET /contacts/{id}/export devuelve todo lo que la cuenta guarda sobre la persona, en un JSON — la solicitud de acceso.
  • GET /contacts/export genera un CSV de toda la base, o de lo que seleccionen los filtros.
Clave restringida a algunos números

Una API key restringida a algunos números solo ve los contactos que tienen conversación en esos números: la lista trae solo a ellos, y un contacto fuera de ese recorte responde 404, como uno que no existe. Con ella puedes leer, editar campos y tags y registrar un opt-out.

Lo que es de toda la base responde 403: exportar, importar, crear, lote, borrar, unir, opt-in, las definiciones de campos y tags y la lista de supresión. Para eso, usa una clave sin restricción.