Volver al blog
Desarrolladores

Actualizar tags y customFields de un contacto por la API de Waspy

PATCH /api/v2/contacts/:id reemplaza la lista de tags. customFields, en el servidor, también pisa el objeto: la doc dice merge. Para el Black Friday, marcá al comprador del CyberMonday sin borrar el resto de la ficha.

· 12 min
Actualizar tags y customFields de un contacto por la API de Waspy

Respuesta corta: para actualizar una ficha hacés PATCH https://api.waspytech.com/api/v2/contacts/{id} con Authorization: Bearer wspy_... y el scope contacts:write. tags reemplaza la lista. customFields, en el servidor, también reemplaza el objeto: la referencia de contactos dice que hace merge, y el PATCH de apps/api/src/routes/v2/contacts.ts pisa la columna. Este pedido no envía un WhatsApp.

Crear la ficha es otra nota: contactos por API. Esta es la de después del CyberMonday: marcar a quien ya compró, con el tag cm2026 y la fecha en ultima_compra, para no mandarle marketing de Black Friday el viernes 27 a quien acaba de pagar.

La API está en los planes Scale y Enterprise. El detalle está en Planes y en la API.

Qué podés cambiar

CampoSi lo mandásSi lo omitís
nameReemplaza el nombre. El schema acepta null para vaciarloQueda como está
emailTiene que ser un email, o nullQueda como está
phoneNumberTiene que pasar el chequeo E.164. El handler no busca duplicados antes de escribir: el 409 CONTACT_ALREADY_EXISTS es del POSTQueda como está
tagsReemplaza el array enteroQueda como está
customFieldsEn el código, reemplaza el objeto. La doc publicada dice mergeQueda como está
El body no acepta optedIn. Ese campo sale en el GET porque la columna nace en true. No es un consentimiento para campañas. El opt-in está en cómo pedirlo.

No hay DELETE /api/v2/contacts. El PATCH tampoco toma Idempotency-Key: ese header es de POST /api/v2/messages.

Marcar compradores del CyberMonday

El caso es una tienda que ya cobró entre el 2 y el 4. El viernes 6 no es para ofrecerles otra promo. Es para dejar la ficha lista. El tag cm2026 y ultima_compra sirven el 27 para excluir a quien ya compró, no para escribirles hoy.

Primero leé. Si mandás customFields con una sola clave, el resto se pierde.

curl -sS "https://api.waspytech.com/api/v2/contacts/UUID-DEL-CONTACTO" \
  -H "Authorization: Bearer $WASPY_API_KEY"

Scope: contacts:read. Un id de otra cuenta, o uno que no existe, es 404 CONTACT_NOT_FOUND.

Después reenviá tags y campos completos:

curl -sS -X PATCH "https://api.waspytech.com/api/v2/contacts/UUID-DEL-CONTACTO" \
  -H "Authorization: Bearer $WASPY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tags": ["vip", "cm2026"],
    "customFields": {
      "local": "Palermo",
      "ultima_compra": "2026-11-03"
    }
  }'

Si la ficha tenía vip y el body trae solo ["cm2026"], vip desaparece. Si tenía local y el body trae solo ultima_compra, local desaparece, aunque la doc diga merge. El 200 devuelve la ficha ya pisada: mirá data.tags y data.customFields antes de seguir con el próximo.

El mismo cuidado, en fetch, leyendo primero:

const headers = {
  Authorization: `Bearer ${process.env.WASPY_API_KEY}`,
  "Content-Type": "application/json",
};
const id = "UUID-DEL-CONTACTO";
const got = await fetch(`https://api.waspytech.com/api/v2/contacts/${id}`, { headers });
const { data } = await got.json();
const tags = Array.from(new Set([...(data.tags ?? []), "cm2026"]));
const customFields = { ...(data.customFields ?? {}), ultima_compra: "2026-11-03" };
await fetch(`https://api.waspytech.com/api/v2/contacts/${id}`, {
  method: "PATCH",
  headers,
  body: JSON.stringify({ tags, customFields }),
});

Eso conserva lo que ya estaba y suma el dato del evento. No es un merge del servidor: es un read-modify-write del cliente.

Listar por tag y por búsqueda

GET /api/v2/contacts pagina con page (default 1) y pageSize (default 20, máximo 100). search mira nombre, teléfono y email. El + del teléfono va codificado.

El query que filtra es tags, no tag. La página de contactos muestra tag. El handler lee q.tags y parte por coma. Un tag=cm2026 solo no recorta la lista.

curl -sS "https://api.waspytech.com/api/v2/contacts?tags=cm2026&pageSize=50" \
  -H "Authorization: Bearer $WASPY_API_KEY"

Con tags=cm2026,vip entra quien tenga al menos una de las dos. El SQL es el solapamiento de arrays (&&). El OpenAPI del repo dice “AND”: el handler no exige las dos. Para el segmento de compradores del evento, tags=cm2026 alcanza.

search y tags se pueden combinar. meta trae page, pageSize, total y requestId.

Si todavía no existe: POST, 409 y 400

Crear sigue siendo POST /api/v2/contacts, scope contacts:write. phoneNumber es el único campo obligatorio. El chequeo es + y entre 7 y 15 dígitos. Un celular argentino que después acepta bien WhatsApp es +549 más 10 dígitos, como +5491126032641. Un +549 más corto puede pasar este chequeo y fallar en Meta.

curl -sS -X POST https://api.waspytech.com/api/v2/contacts \
  -H "Authorization: Bearer $WASPY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+5491126032641",
    "name": "María García",
    "email": "maria@email.com",
    "tags": ["cm2026"],
    "customFields": { "ultima_compra": "2026-11-03" }
  }'

El 201 trae data.id. Si ese teléfono ya está, no hay upsert:

{
  "error": {
    "code": "CONTACT_ALREADY_EXISTS",
    "message": "Contact with this phone number already exists",
    "status": 409
  }
}

Un teléfono que no cumple el E.164 es 400 INVALID_PHONE (Phone number must be in E.164 format), con details.field. Sin phoneNumber, 400 MISSING_REQUIRED_FIELD.

El patrón de una importación del checkout:

  1. GET /api/v2/contacts?search=%2B5491126032641.
  2. Si meta.total es 0, POST.
  3. Si ya está, PATCH con la lista y el objeto completos.

Al crear, Waspy emite contact.created si hay una suscripción a ese evento. El PATCH no emite ese evento. El alta del webhook está en webhooks.

El aviso del 27 es otro pedido

El tag no manda el mensaje. El viernes 27, una plantilla de marketing a quien no tiene cm2026 sale por POST /api/v2/messages, con messages:write. En Argentina, desde el 1 de octubre de 2026, ese mensaje entregado ronda ARS 89,56 (~US$0,0618). Quien ya compró no entra en ese envío. Cómo separar las dos listas está en segmentación y en la recompra.

El tope de /api/v2 es 120 pedidos por minuto por cuenta. Al pasarlo, 429 RATE_LIMITED. Los scopes están en scopes.

Para probar la clave, el alta está en Configuración → Claves de API. Diez días sin tarjeta, en el registro.

Tu equipo atendiendo por WhatsApp en minutos

CRM facil pero potente. En 10 minutos tenes a tu equipo trabajando — no necesitas hablar con nadie.