--- title: Conexões description: Permita que seu aplicativo aja em nome de um usuário em serviços de terceiros via OAuth. icon: plug --- Conexões são credenciais que um usuário mantém para um serviço externo (Linear, GitHub, Slack, ...). Seu app declara **como** essas credenciais são obtidas — um **provedor de conexão** — e as consome em tempo de execução para fazer chamadas autenticadas à API de terceiros. Atualmente, apenas o OAuth 2.0 tem suporte. Tipos de credenciais futuros (tokens de acesso pessoal, chaves de API, autenticação básica) serão conectados à mesma interface — apps que já usam `defineConnectionProvider({ type: 'oauth', ... })` não precisarão migrar. Um provedor de conexão descreve o handshake OAuth de que seu app precisa. O usuário clica em "Adicionar conexão" nas configurações do seu app, conclui a tela de consentimento do provedor e uma linha `ConnectedAccount` é criada no seu workspace. Uma configuração funcional precisa de **dois arquivos** — o provedor de conexão e uma declaração correspondente de `serverVariables` em `defineApplication` que contém as credenciais do cliente OAuth. ```ts src/connection-providers/linear-connection.ts import { defineConnectionProvider } from 'twenty-sdk/define'; export default defineConnectionProvider({ universalIdentifier: '9c7d1f5e-6a0b-4d44-be0c-3f8b5a9d4e6f', name: 'linear', displayName: 'Linear', icon: 'IconBrandLinear', type: 'oauth', oauth: { authorizationEndpoint: 'https://linear.app/oauth/authorize', tokenEndpoint: 'https://api.linear.app/oauth/token', scopes: ['read', 'write'], // These must match keys in `defineApplication.serverVariables` below. clientIdVariable: 'LINEAR_CLIENT_ID', clientSecretVariable: 'LINEAR_CLIENT_SECRET', // Optional: defaults to 'json'. Some providers (Linear, Slack) want // 'form-urlencoded' for the token request. tokenRequestContentType: 'form-urlencoded', // Optional: defaults to true. Disable only if the provider rejects PKCE. usePkce: false, // Optional: extra query params on the authorize URL. // authorizationParams: { prompt: 'consent' }, // Optional: provider's RFC 7009 token revocation endpoint, called on disconnect. // revokeEndpoint: 'https://example.com/oauth/revoke', }, // Optional: a logic function in this app to run right after a connection is // established. See "Run a logic function on connect". // onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' }, // Optional: a logic function in this app to run right after a connection is // removed. See "Run a logic function on disconnect". // onDisconnectLogicFunction: { universalIdentifier: '4d5e6f70-...-...' }, }); ``` ```ts src/application.config.ts import { defineApplication } from 'twenty-sdk/define'; export default defineApplication({ universalIdentifier: '...', displayName: 'Linear', description: 'Connect Linear to Twenty.', // OAuth client credentials live on the app registration (one OAuth app per // Twenty server, configured by the admin) — not per-workspace. Declare them // as serverVariables so the admin can fill them in once for all installs. serverVariables: { LINEAR_CLIENT_ID: { description: 'OAuth client ID from your Linear OAuth application.', isSecret: false, isRequired: true, }, LINEAR_CLIENT_SECRET: { description: 'OAuth client secret from your Linear OAuth application.', isSecret: true, isRequired: true, }, }, }); ``` Pontos-chave: * `name` é a string de identificador exclusivo usada em `listConnections({ providerName })` (kebab-case, deve corresponder a `^[a-z][a-z0-9-]*$`). * `displayName` aparece na aba de configurações do app e na lista de ferramentas de IA. * `clientIdVariable` / `clientSecretVariable` são **nomes**, não valores — devem corresponder às chaves declaradas em `defineApplication.serverVariables`. Os `client_id` e `client_secret` reais são inseridos pelo administrador do servidor por meio da interface de registro do app e nunca são versionados no seu repositório. * Use `serverVariables` (não `applicationVariables`) — as credenciais OAuth são do servidor como um todo e há um app OAuth por servidor do Twenty. * Até que ambos os `serverVariables` sejam preenchidos, a aba de configurações do app mostra uma dica "precisa de administrador do servidor" e o botão "Adicionar conexão" fica desativado. * `type: 'oauth'` é o único valor compatível atualmente. O discriminador é compatível com versões futuras: tipos futuros (`'pat'`, `'api-key'`, ...) adicionarão novos blocos de subconfiguração ao lado de `oauth`. O URL de callback do OAuth que seu provedor precisa adicionar à lista de permissões é: ``` https:///auth/apps/callback ``` Alguns provedores fornecem dados no momento da conexão que você precisa manter antes que a conexão possa ser usada — o exemplo clássico é o Slack, em que a resposta OAuth identifica o `team_id` do workspace pelo qual os eventos de entrada serão indexados. Defina `onConnectLogicFunction` para fazer referência a uma função lógica no mesmo app (pelo seu `universalIdentifier`), e ela será executada logo após o `ConnectedAccount` ser criado. ```ts src/connection-providers/slack-connection.ts export default defineConnectionProvider({ universalIdentifier: '...', name: 'slack', displayName: 'Slack', type: 'oauth', oauth: { /* ... */ }, // Runs claimSlackTeam after every successful Slack connection. onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666', }, }); ``` O hook é executado **de forma assíncrona no workspace que está se conectando** (ele é enfileirado, não aguardado), portanto um hook lento ou com falha nunca bloqueia ou quebra o callback OAuth — torne-o idempotente e faça com que ele mesmo gerencie suas próprias novas tentativas. O manipulador recebe: ```ts type OnConnectPayload = { connectionProviderId: string; connectionProviderName: string; // e.g. 'slack' connectedAccountId: string; }; ``` A partir daí, use `getConnection(connectedAccountId)` para ler o token de acesso atualizado e chamar a API do provedor (por exemplo, Slack `auth.test`) ou persistir um mapeamento com o [armazenamento de chave-valor](/l/pt/developers/extend/apps/logic/key-value-store). Tudo o que um app reivindica no momento da conexão precisa ser liberado quando a conexão é encerrada. Uma integração do Slack que reivindica um `team_id` ao conectar, por exemplo, precisa liberar essa reivindicação para que outro espaço de trabalho possa conectar a mesma equipe do Slack. Defina `onDisconnectLogicFunction` para fazer referência a uma função lógica no mesmo app, e ela será executada logo após o `ConnectedAccount` ser excluído. ```ts src/connection-providers/slack-connection.ts export default defineConnectionProvider({ universalIdentifier: '...', name: 'slack', displayName: 'Slack', type: 'oauth', oauth: { /* ... */ }, // Runs releaseSlackTeam after every Slack disconnection. onDisconnectLogicFunction: { universalIdentifier: '4470aba8-5ff5-4800-88db-2a427cd8677c', }, }); ``` Assim como o hook de on-connect, ele é executado **assincronamente no espaço de trabalho que está sendo desconectado** e nunca bloqueia a desconexão. O handler recebe o mesmo formato de payload: ```ts type OnDisconnectPayload = { connectionProviderId: string; connectionProviderName: string; // e.g. 'slack' connectedAccountId: string; }; ``` O `ConnectedAccount` já não existe mais quando o hook é executado, então `getConnection(connectedAccountId)` não retorna mais resultado. Tudo o que a limpeza precisar (um `team_id`, um id de assinatura externa) deve ter sido gravado no [key-value store](/l/pt/developers/extend/apps/logic/key-value-store) no momento da conexão, indexado por `connectedAccountId`. O hook é disparado quando uma conexão é removida por conta própria. A desinstalação do app remove suas conexões por meio de uma cascata de banco de dados; portanto, o hook não é executado nesse caso. Declare um `uninstallLogicFunction` em `defineApplication` para esse caminho: ele é executado antes de os metadados do app serem excluídos, então ainda pode chamar `listConnections` e limpar o que tiver restado. Dentro de um handler de função de lógica, `listConnections({ providerName })` retorna as linhas `ConnectedAccount` deste app para o provedor fornecido, com tokens de acesso atualizados. ```ts src/logic-functions/handlers/create-linear-issue-handler.ts import { listConnections } from 'twenty-sdk/logic-function'; export const createLinearIssueHandler = async (input: { teamId?: string; title?: string; }) => { if (!input.teamId || !input.title) { return { success: false, error: 'teamId and title are required' }; } const connections = await listConnections({ providerName: 'linear' }); // Workspace-shared credentials win when present; fall back to the first // user-visibility one. For HTTP-route triggers you typically pick the // request user's connection via event.userWorkspaceId instead. const connection = connections.find((c) => c.visibility === 'workspace') ?? connections[0]; if (!connection) { return { success: false, error: 'Linear is not connected. Open the app settings and click "Add connection".', }; } // Use connection.accessToken to call the third-party API. const response = await fetch('https://api.linear.app/graphql', { method: 'POST', headers: { Authorization: `Bearer ${connection.accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: `mutation { issueCreate(input: { teamId: "${input.teamId}", title: "${input.title}" }) { success } }`, }), }); return { success: response.ok }; }; ``` Cada conexão tem: | Campo | Descrição | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | ID de linha exclusivo; passe para `getConnection(id)` para buscar novamente um único registro | | `visibility` | `'user'` (privada para um membro do workspace) ou `'workspace'` (compartilhada com todos os membros) | | `scopes` | Permissões OAuth concedidas pelo provedor de origem (distintas de `visibility` — não têm relação) | | `userWorkspaceId` | O id de userWorkspace do proprietário — útil para selecionar "a conexão do usuário da requisição" em gatilhos de rota HTTP | | `accessToken` | Token de acesso OAuth recente (atualizado automaticamente se estiver expirado) | | `nome` | O nome de exibição da conexão (derivado automaticamente no callback do OAuth, renomeável pelo usuário) | | `ligar` | O e-mail da conta de upstream foi atualizado em cada reconexão; resolvido do OIDC `id_token` retornado na troca de token, retornando para o e-mail de 20 usuários quando o provedor não retornar um | | `authFailedAt` | Definido quando a atualização mais recente falhou; o usuário deve reconectar | Pontos-chave: * Passe `{ providerName }` para filtrar por provedor; omita para obter todas as conexões que este app possui em todos os provedores. * O servidor atualiza transparentemente o token de acesso antes de retornar. Seu handler sempre vê um token utilizável (ou `authFailedAt` definido). * `getConnection(id)` é o equivalente de uma única linha. Quando um usuário clica em "Adicionar conexão", é solicitado que escolha uma visibilidade: * **Apenas para mim** — a credencial é privada para o usuário que a conectou. Qualquer função de lógica chamada em seu nome (gatilho de rota HTTP com `isAuthRequired: true`) a vê; gatilhos cron e eventos de banco de dados não. * **Compartilhada no workspace** — qualquer membro do workspace pode usar a credencial. Gatilhos de cron / banco de dados também a veem, pois não há um usuário da requisição. Use a adequada para cada handler: ```ts // HTTP-route trigger — prefer the request user's own connection. const conn = connections.find((c) => c.userWorkspaceId === event.userWorkspaceId) ?? connections.find((c) => c.visibility === 'workspace'); // Cron trigger — no request user; only shared credentials are sensible. const conn = connections.find((c) => c.visibility === 'workspace'); ``` Várias conexões por (usuário, provedor) são permitidas, então o mesmo usuário pode manter "Linear pessoal" e "Linear de trabalho" lado a lado. Para cada provedor de conexão, o administrador do servidor precisa primeiro registrar um app OAuth no serviço de terceiros. 1. Acesse as configurações de desenvolvedor do provedor (por exemplo, https://linear.app/settings/api/applications/new). 2. Defina a **URI de redirecionamento** como `\/auth/apps/callback`. 3. Copie o **ID do cliente** e o **Segredo do cliente** gerados. 4. Abra o app instalado no Twenty como administrador do servidor → defina os valores nos `serverVariables` correspondentes. 5. Os membros do workspace podem então adicionar conexões na seção **Conexões** de cada app.