Pular para o conteúdo
BetaDocumentação em validação contínua. Comportamento descrito pode divergir do servidor — abra um chamado no app se algo não bater.
Parceiros

Construir um app no marketplace

Apps no marketplace do MIX são pacotes TypeScript que rodam no nosso backend. Você define um manifest, expõe actions tipadas em Zod e triggers de eventos do provider. O MIX cuida de OAuth, refresh, armazenamento de credenciais e UI de configuração.

Informação
Programa de parceiros
O marketplace está aberto a parceiros aprovados. Pra publicar, abra um chamado de suporte no app web pedindo "contato comercial — programa de partners" com (a) descrição da integração, (b) link da empresa e (c) volume esperado.

Anatomia de um app

Todo app exporta um único objeto via registerProvider(). O contrato é o seguinte:

src/index.ts
import { z } from "zod";
import { registerProvider } from "@/marketplace/registry";

export default registerProvider({
  manifest: {
    slug: "minha-integracao",
    name: "Minha Integração",
    category: "communication",
    iconUrl: "https://exemplo.com/icon.svg",
    tagline: "Conecta o MIX ao serviço X.",
    description:
      "Descrição longa em prosa. Aparece na página do app.",
    auth: { kind: "oauth2" }, // ou "api_key" | "none"
    scopesRequested: ["read", "write"],
    website: "https://exemplo.com",
  },

  oauth: {
    authorizationUrl: "https://provider.com/oauth/authorize",
    tokenUrl: "https://provider.com/oauth/token",
    scopeJoin: " ", // como concatenar scopes
  },

  actions: {
    sendNotification: {
      label: "Enviar notificação",
      description: "Manda push para o usuário.",
      input: z.object({
        userId: z.string(),
        message: z.string().min(1).max(200),
      }),
      handler: async ({ input, credentials }) => {
        const res = await fetch("https://provider.com/api/v1/notify", {
          method: "POST",
          headers: { Authorization: `Bearer ${credentials.accessToken}` },
          body: JSON.stringify(input),
        });
        if (!res.ok) throw new Error(`provider error: ${res.status}`);
        return { ok: true };
      },
    },
  },

  triggers: {
    notificationDelivered: {
      label: "Notificação entregue",
      description: "Dispara quando o usuário recebe a mensagem.",
      output: z.object({
        userId: z.string(),
        deliveredAt: z.string().datetime(),
      }),
    },
  },

  webhook: {
    path: "events",
    verify: async ({ rawBody, headers, credentials }) => {
      // Implemente verificação HMAC do provider.
      return true;
    },
    handle: async ({ event, emit }) => {
      if (event.type === "notification.delivered") {
        emit("notificationDelivered", {
          userId: event.userId,
          deliveredAt: event.timestamp,
        });
      }
    },
  },

  onInstall: async ({ tenantId, credentials }) => {
    // Provisionar recursos no provider, se necessário.
  },
  onUninstall: async ({ tenantId, credentials }) => {
    // Limpar resources externos.
  },
});

Autenticação

Três modos suportados em manifest.auth.kind:

  • oauth2 — fluxo authorization_code padrão. Você fornece authorizationUrl e tokenUrl; o MIX orquestra o resto. Suporta PKCE quando o provider exige.
  • api_key — usuário cola uma key no app. Ela vai criptografada pra credentials.apiKey nas suas actions.
  • none — apps que não precisam autenticar (ex.: módulos internos do MIX, webhooks públicos sem segredo).

Fluxo OAuth detalhado

MIX orquestra (você não precisa rodar)
// 1. MIX redireciona usuário para provider.com/oauth/authorize
//    com ?client_id=...&redirect_uri=...&state=...

// 2. Após autorização, provider redireciona pra:
//    https://api.usemix.app/marketplace-callback?code=...&state=...

// 3. MIX faz POST pro tokenUrl trocando code por accessToken:
const res = await fetch("https://provider.com/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code,
    redirect_uri,
    client_id,
    client_secret,
  }),
});
const { access_token, refresh_token, expires_in } = await res.json();

// 4. MIX armazena criptografado em `app_installations.credentials`
//    com tenant secret. Refresh é automático antes de expirar.

Actions tipadas

Actions são funções que o MIX (ou automações do usuário) chama para executar algo no provider. Inputs são validados via Zod — schemas ruins falham antes de chegar no handler.

  • label e description aparecem na UI de configuração de automação.
  • input é um schema Zod. Use .optional() pros campos opcionais.
  • handler recebe { input, credentials, tenantId, logger } e retorna o resultado.
  • Erros lançados são logados em app_action_runs e aparecem no audit log do tenant.

Triggers (eventos do provider)

Triggers expõem eventos do provider como gatilhos pra automações no MIX. Você declara o nome e o schema do payload — quando o webhook chega, você chama emit() dentro do webhook.handle com o nome do trigger e os dados.

Usuários do MIX então criam automações tipo "Quando trigger X, cria deal" sem precisar saber nada do payload bruto.

Webhooks (recepção do provider)

Se o seu app expõe triggers, é provável que precise receber webhooks do provider. O MIX já provê uma URL única por instalação:

text
https://api.usemix.app/marketplace-webhooks/<app-slug>/<installation-id>

Quando esse endpoint recebe POST, o MIX chama seu webhook.verify() e em sucesso chama webhook.handle(). Sempre verifique a assinatura — receber payload sem verificar é a mesma falha de segurança dos webhooks de saída.

webhook.verify()
verify: async ({ rawBody, headers, credentials }) => {
  const sig = headers["x-provider-signature"];
  const expected = crypto
    .createHmac("sha256", credentials.webhookSecret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(sig, "hex"),
    Buffer.from(expected, "hex"),
  );
}

Ciclo de vida

  • onInstall — chamado quando um tenant ativa o app. Recebe credenciais. Use pra registrar webhooks no provider, criar recursos remotos, validar permissões.
  • onUninstall — quando o tenant desconecta. Use pra limpar webhooks/recursos remotos. Sempre seja idempotente — pode ser chamado mais de uma vez.

Testar localmente

  1. Solicite o repositório template e os exemplos atualizados em contato com o time de partners (ver final desta página).
  2. Solicite um workspace de desenvolvimento via suporte no app (não use produção pra dev).
  3. Use ngrok / Cloudflare Tunnel pra expor seu provider mockado.
  4. Rode o sandbox: pnpm marketplace:sandbox. Ele monta seu manifest contra o tenant de dev e simula chamadas.

Publicação

  1. App passa por code review interno.
  2. Você assina o App Partner Agreement (cobertura de incidentes, LGPD, suporte).
  3. App vai pra preview — usuários veem com badge "Beta" e telemetria de uso é enviada pra você semanalmente.
  4. Após 30 dias sem incidentes, app sai de preview e fica público.
Dica
Programa fechado
O programa de partners é fechado. Pra ganhar acesso ao registry/SDK e ao programa, abra um chamado de suporte no app web com seu caso de uso.

A gente usa cookies pra entender o que funciona e o que não funciona aqui. Sem terceiros — dado fica conosco. Política.