Exemplos

.md

Receitas

Quatro integrações inteiras, do jeito que a gente faria. Copie e troque os ids.

1. O lead do site vira conversa

O visitante preenche o formulário e, em vez de entrar numa planilha para alguém ligar depois, já recebe a primeira mensagem — no minuto em que o interesse existe.

No backend do seu site, depois de validar o formulárioJavaScript
const r = await fetch("https://linkzap.orion.moe/api/webhooks/channel/" + process.env.LINKZAP_CHANNEL_ID, {
  method: "POST",
  headers: {
    "x-api-key": process.env.LINKZAP_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    phone: form.telefone,
    name: form.nome,
    email: form.email,
    message: `Oi, ${form.nome.split(" ")[0]}! Recebemos o seu contato sobre ${form.assunto}. Já te respondo por aqui.`,
    tags: ["lead-site"],
    fields: { origem: form.utm_source ?? "direto", assunto: form.assunto },
    optIn: true,   // a pessoa marcou a caixa de consentimento no formulário
  }),
});

if (!r.ok) {
  // 409 = o número não está pronto. O CONTATO foi salvo mesmo assim:
  // registre para a equipe retomar, não perca o lead.
  console.error("linkzap:", r.status, await r.text());
}

Uma variação que costuma render mais

Troque message por flowId. Em vez de uma frase solta cuja resposta cai na fila sem contexto, o contato entra num fluxo que já pergunta o que precisa saber e só chama um humano quando vale a pena.

2. O seu sistema avisa o cliente

Pedido aprovado, consulta confirmada, boleto vencendo. O caso mais comum de todos.

Uma função para reusarJavaScript
async function avisar(telefone, texto) {
  const r = await fetch("https://linkzap.orion.moe/api/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LINKZAP_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      channelId: process.env.LINKZAP_CHANNEL_ID,
      phone: telefone,
      text: texto,
    }),
  });

  if (r.status === 409) {
    // O número não está pronto para enviar; o motivo vem em palavras.
    const { error } = await r.json();
    throw new Error("canal indisponível: " + error);
  }
  if (r.status === 429) {
    // Respeite o Retry-After em vez de tentar em laço.
    throw new Error("freio: tente em " + r.headers.get("retry-after") + "s");
  }
  if (!r.ok) throw new Error(await r.text());

  const { data } = await r.json();
  return data.messageId;   // guarde: reenviar sem checar manda duas vezes
}

Na conta oficial, cuidado com a janela

Se o número é provider: "cloud", texto livre só sai se o contato falou com você nas últimas 24 horas. Para avisos que partem de você, o caminho é um template aprovado — que hoje se dispara pelo painel, por um fluxo, por uma sequência ou por uma campanha. Nos números por QR não há essa restrição.

3. Espelhar as conversas no seu banco

Para o seu time comercial ver o histórico de WhatsApp dentro do seu próprio sistema, sem trocar de tela. Escute os eventos e escreva no seu banco — nada de varrer a API de tempos em tempos.

O receptor, com assinatura e idempotênciaJavaScript
app.post("/linkzap", express.raw({ type: "application/json" }), async (req, res) => {
  if (!assinaturaConfere(req)) return res.sendStatus(401);   // ver "Webhooks de saída"

  const entrega = req.header("x-linkzap-delivery");
  const { event, data } = JSON.parse(req.body.toString("utf8"));

  // Responda ANTES de processar: o prazo é de 10 segundos.
  res.sendStatus(200);

  // A mesma entrega pode chegar duas vezes — o id é a chave.
  if (await jaProcessei(entrega)) return;
  await marcarProcessado(entrega);

  switch (event) {
    case "message.received":
    case "message.sent":
      await db.mensagens.upsert({
        externo: data.messageId,
        conversa: data.conversationId,
        telefone: data.phone,
        texto: data.text,
        entrada: event === "message.received",
        quando: data.occurredAt,
      });
      break;
    case "contact.opt_out":
      // Respeite no SEU sistema também: nada de incluí-lo no próximo disparo.
      await db.contatos.marcarOptOut(data.phone);
      break;
  }
});

Para trazer o que aconteceu antes de você ligar o webhook, percorra GET /api/v1/conversations e, para cada uma, GET /api/v1/messages?conversationId=…. Faça isso uma vez, na carga inicial — o resto vem pelos eventos.

4. Um chatbot seu respondendo

Você já tem a sua própria automação e quer que ela atenda por aqui. Funciona, com um cuidado que economiza um bug difícil.

Receber, pensar, responderJavaScript
// No receptor de webhook, depois de conferir a assinatura:
if (event === "message.received") {
  const resposta = await meuBot(data.text, data.conversationId);

  await fetch("https://linkzap.orion.moe/api/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LINKZAP_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      channelId: data.channelId,
      conversationId: data.conversationId,   // responda NA conversa
      text: resposta,
    }),
  });
}

Desligue a Aura nesses canais

Uma mensagem enviada pela API não silencia a automação daqui — quem faz isso é uma pessoa respondendo pelo painel ou pelo celular. Se a Aura ou um fluxo também estiverem atendendo esse número, o contato recebe duas respostas para a mesma pergunta. Escolha um: ou a automação do LinkZap, ou a sua.

Repare também que a sua própria resposta gera um message.sent no seu receptor. Se o seu código reage a esse evento, garanta que ele não se responda em laço.

Continue por aqui

Os detalhes de cada rota estão na referência, e a verificação da assinatura em três linguagens está em Webhooks de saída. Ficou faltando alguma coisa? O suporte quer saber qual — é assim que a gente decide o que entra primeiro.