waiaconnect

Documentación / Completo

Recorrido del integrador: alta y webhooks 100 % por API

GET /v1/onboarding-links/{id}

Markdown

Si das de alta a tus clientes desde tu sistema, sin entrar al panel, éste es el camino entero, en cinco pasos.

1. Un enlace por cliente. POST /v1/onboarding-links con tu externalId (tu id para ese cliente) y webhook: { url }. Te devolvemos la url para tu cliente y el secreto whsec_… del webhook una sola vez: guardalo ya, firma todo lo de ese cliente. Un externalId que ya usa otro número de tu cuenta da 409 en este momento, no al final del alta.

2. Tu cliente conecta. Abre la url, entra con su cuenta de Meta y elige su número. Al terminar, el número nace con su webhook (la URL del enlace) y te llega onboarding.completed ahí, con connection.id (conn_…), tu externalId, reconnected y el webhookId. Un número nuevo trae además connection.created, como siempre.

3. Si no termina, te enterás igual. Cada intento que se corta (falló, lo canceló o lo abandonó) manda onboarding.failed a la URL del webhook del enlace, firmado con el mismo secreto: reason (meta_error, incomplete, timeout, cancelled, abandoned…), metaErrorCode y metaMessage si Meta los dio, y linkUsable (si tu cliente puede reintentar con el mismo enlace). Si preferís preguntar: GET /v1/onboarding-links?externalId=… o GET /v1/onboarding-links/{id} traen outcome (pending, completed o failed, con el mismo motivo).

4. El webhook del número es tuyo. Lo ves y lo cambiás con /v1/webhooks?externalId=…: URL, eventos, cabecera fija, rotar el secreto, probarlo.

5. La baja. Cuando el número se desconecta (desde el panel, porque el negocio nos quitó el acceso en Meta o porque se revocó el permiso), su webhook queda apagado (active: false, disabledReason: "connection_disconnected") y deja de recibir. Si se reconecta el mismo número, vuelve solo; los que pausaste vos o los que apagamos por fallar, no.

# 1) one link per end-customer: your id + the webhook its number will have
curl -X POST https://api.waiaconnect.com/v1/onboarding-links \
  -H "Authorization: Bearer wc_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"externalId": "pharmacy-42", "label": "Pharmacy 42", "mode": "coexistence",
       "webhook": {"url": "https://api.yourcompany.com/hooks/connect"}}'
# → 201 { "publicId": "onb_…", "url": "https://…/c#<token>", "expiresAt": "…",
#         "webhook": { "secret": "whsec_…", "createdOnCompletion": true, … } }   ← store the secret NOW

# 2) send "url" to your customer. Then, on https://api.yourcompany.com/hooks/connect:
#    { "type": "onboarding.completed", "connection": { "id": "conn_…", "externalId": "pharmacy-42", … },
#      "data": { "onboardingLinkId": "onb_…", "reconnected": false, "webhookId": "wh_…", … } }
#    { "type": "onboarding.failed", "connection": { "id": null, … },
#      "data": { "externalId": "pharmacy-42", "reason": "meta_error", "metaErrorCode": 2655093,
#                "metaMessage": "The business is already sharing…", "linkUsable": true, … } }

# 3) or ask (polling) by your id
curl "https://api.waiaconnect.com/v1/onboarding-links?externalId=pharmacy-42" -H "Authorization: Bearer wc_live_YOUR_API_KEY"
# → { "links": [ { "id": "onb_…", "status": "used", "connectionId": "conn_…",
#       "outcome": { "state": "completed", "connectionId": "conn_…", "webhookId": "wh_…", … } } ] }
curl https://api.waiaconnect.com/v1/onboarding-links/onb_… -H "Authorization: Bearer wc_live_YOUR_API_KEY"

# 4) the number's webhook, by your id (PATCH to change url/events, rotate-secret, test)
curl "https://api.waiaconnect.com/v1/webhooks?externalId=pharmacy-42" -H "Authorization: Bearer wc_live_YOUR_API_KEY"

# 5) offboarding: disconnecting the number switches its webhook off
#    (active: false, disabledReason: "connection_disconnected"); reconnecting it switches it back on
const H = { "Authorization": "Bearer wc_live_YOUR_API_KEY", "Content-Type": "application/json" };

// 1) one link per end-customer: your id + the webhook its number will have
const link = await (await fetch("https://api.waiaconnect.com/v1/onboarding-links", {
  method: "POST", headers: H,
  body: JSON.stringify({ externalId: "pharmacy-42", label: "Pharmacy 42", mode: "coexistence",
    webhook: { url: "https://api.yourcompany.com/hooks/connect" } })
})).json();
await saveSecret("pharmacy-42", link.webhook.secret); // shown ONCE — it signs this customer's events
await sendToCustomer(link.url);

// 2) your webhook handler (verify the signature first — see «Verify the signature»)
app.post("/hooks/connect", express.raw({ type: "application/json" }), async (req, res) => {
  const evt = JSON.parse(req.body);
  if (evt.type === "onboarding.completed") await markConnected(evt.data.externalId, evt.connection.id);
  if (evt.type === "onboarding.failed") await tellCustomer(evt.data.externalId, evt.data.reason, evt.data.metaErrorCode, evt.data.linkUsable);
  res.sendStatus(200);
});

// 3) or poll by your id
const { links } = await (await fetch("https://api.waiaconnect.com/v1/onboarding-links?externalId=pharmacy-42", { headers: H })).json();
console.log(links[0]?.outcome); // { state: "pending" | "completed" | "failed", reason, metaErrorCode, … }
<?php
function connect($method, $url, $body = null) {
  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer wc_live_YOUR_API_KEY", "Content-Type: application/json"],
    CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
  ]);
  return json_decode(curl_exec($ch), true);
}
// 1) one link per end-customer: your id + the webhook its number will have
$link = connect("POST", "https://api.waiaconnect.com/v1/onboarding-links", [
  "externalId" => "pharmacy-42", "label" => "Pharmacy 42", "mode" => "coexistence",
  "webhook" => ["url" => "https://api.yourcompany.com/hooks/connect"],
]);
save_secret("pharmacy-42", $link["webhook"]["secret"]); // shown ONCE

// 2) in your webhook handler: onboarding.completed / onboarding.failed (data.reason, data.metaErrorCode)
// 3) or poll by your id
$links = connect("GET", "https://api.waiaconnect.com/v1/onboarding-links?externalId=pharmacy-42")["links"];
$outcome = $links[0]["outcome"] ?? null; // state: pending | completed | failed
import requests
H = {"Authorization": "Bearer wc_live_YOUR_API_KEY"}

# 1) one link per end-customer: your id + the webhook its number will have
link = requests.post("https://api.waiaconnect.com/v1/onboarding-links", headers=H, json={
    "externalId": "pharmacy-42", "label": "Pharmacy 42", "mode": "coexistence",
    "webhook": {"url": "https://api.yourcompany.com/hooks/connect"},
}).json()
save_secret("pharmacy-42", link["webhook"]["secret"])  # shown ONCE

# 2) in your webhook handler: onboarding.completed / onboarding.failed (data.reason, data.metaErrorCode)
# 3) or poll by your id
links = requests.get("https://api.waiaconnect.com/v1/onboarding-links", headers=H, params={"externalId": "pharmacy-42"}).json()["links"]
outcome = links[0]["outcome"] if links else None  # state: pending | completed | failed

Sin webhook en el enlace, onboarding.completed y onboarding.failed van a tus webhooks de cuenta (en onboarding.failed, connection.id viene en null: tu externalId va en data).

Un webhook con events explícito que no incluye estos tipos no los recibe; uno con todos los eventos empieza a recibirlos.

En los enlaces de alta, connectionId sale ahora como conn_…; el id interno queda en legacyConnectionId, que se va a retirar.

outcome.state = failed también cuando el enlace venció (link_expired) o lo anulaste (link_revoked) sin que tu cliente terminara.