Exolve 35,17 Classificação 116 Assinantes Assinar Katner 26 minutos atrás Protegendo Números de Telefone Pessoais em Marketplaces: Conectando Clientes e Prestadores Médio 9 min 761 Blog da empresa Exolve Infraestrutura de TI * Python
- Desenvolvimento para e-commerce
- Segurança da informação
- Tutorial
Se você está desenvolvendo um marketplace ou serviço onde o cliente se comunica com especialistas em campo, é importante manter as chamadas dentro da plataforma. Quando o prestador e o cliente entram em contato direto, a empresa perde a comissão, o histórico de comunicação e as vendas repetidas.
Evitar completamente a troca de números é impossível, pois, em uma reunião, o prestador e o cliente podem concordar diretamente. Mas se o cliente não quiser deixar seu número pessoal para os prestadores e preferir as garantias e oportunidades da sua plataforma, esse cenário ajuda a manter o contato protegido.
Neste material, montaremos esse cenário em Python, usando o Bitrix24 em vez de um banco de dados. A solução pega o contexto do CRM no momento da chamada e, por meio do MTS Exolve, conecta o cliente, o prestador ou o suporte.
Esquema geral de trabalho
Nesse cenário, um número intermediário único se torna o ponto de entrada público para a chamada. O prestador liga para o cliente por meio desse número. Quando o cliente retorna a ligação, o aplicativo verifica a transação atual no Bitrix24 e seleciona o destinatário. Assim, a comunicação permanece vinculada ao pedido e ao prestador responsável.
O prestador entra na conta pessoal por meio do código do SMS. Em seguida, seleciona o pedido e clica no botão "Entrar em contato com o cliente". Em seguida, o serviço coleta seu contexto de trabalho do Bitrix24: transações ativas, endereço e número do cliente
Quando o contexto é coletado, o MTS Exolve via Callback API liga para o cliente e para o prestador e, em seguida, os conecta por meio de um número intermediário
Stack: Python 3.10+, Streamlit, Flask, Bitrix24 REST API, SMS API e Callback API MTS Exolve.
Arquitetura da solução
O serviço não possui seu próprio banco de dados e fila. O Streamlit armazena o estado de autorização de curta duração, e o estado de trabalho dos pedidos permanece no Bitrix24. Devido a isso, o aplicativo conecta o CRM e a telefonia e não duplica os dados comerciais.
A conta pessoal do prestador é montada em app.py e authservice.py. App.py é responsável pela interface, estado da sessão e ações do usuário. Authservice.py gera um código único e o envia ao prestador via SMS API . Após verificar o código, o usuário entra na conta pessoal com os pedidos.
A integração com Bitrix24 e MTS Exolve é feita em bitrix_integration.py, auth_service.py e exolve_voice.py. O primeiro módulo lê e altera os dados do Bitrix24. Auth_service.py é responsável pela autenticação do prestador via SMS. Exolve_voice.py inicia chamadas de retorno via número intermediário. O Bitrix24 armazena o contexto e o cliente, e o MTS Exolve fornece canais com SMS e chamadas.
Chamadas recebidas são processadas por webhook_router.py. O módulo recebe um webhook da telefonia, procura o contexto ativo do cliente e retorna um JSON com o destinatário da chamada. As chaves e os números são coletados em config.py.
Pré-requisitos
O projeto precisa apenas de dependências Python e variáveis de ambiente para MTS Exolve e Bitrix24. Dois processos são iniciados aqui: a conta pessoal do prestador no Streamlit e o servidor para receber webhooks no Flask. O primeiro atende às ações do usuário, o segundo recebe eventos de entrada da telefonia.
bashpython -m venv .venv source .venv/bin/activate pip install -r requirements.txt
Conjunto mínimo de variáveis:
EXOLVE_API_KEY=***
AUTH_POOL_NUMBER=7999XXXXXXX
SINGLE_SERVICE_NUMBER=7800XXXXXXX
SUPPORT_NUMBER=7800YYYYYYY
BITRIX_WEBHOOK=https://your-domain.bitrix24.ru/rest/...
Para um teste local, basta iniciar ambos os processos e enviar o webhook manualmente. Para tráfego de entrada real, o Flask deve estar acessível externamente.
Passo 1. Autorizamos o prestador
O prestador insere seu número de telefone na interface do Streamlit, o serviço gera um código único e o envia por SMS. O usuário insere o código no formulário, e o aplicativo o compara com o valor que foi temporariamente salvo na sessão.
python# auth_service.py import requests from config import Config def send_flash_call(target_phone: str): auth_number = Config.AUTH_POOL_NUMBER url = "https://api.exolve.ru/voice/v1/MakeCall" headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"} payload = { "number": auth_number, "destination": target_phone, "record": False, "time_limit": 5, } try: resp = requests.post(url, headers=headers, json=payload, timeout=5) resp.raise_for_status() return auth_number[-4:] except Exception as e: print(f"Erro Flash Call: {e}") return None
Em seguida, a interface salva o código gerado no estado de curta duração e o compara com aquele que o usuário inseriu no formulário.
python# app.py if not st.session_state.auth: phone = st.text_input("Seu telefone", placeholder="79990000000") if st.button("Obter código de acesso"): with st.spinner("Ligando..."): code = send_flash_call(phone) if code: st.session_state.verification = code st.success("Aguarde a chamada de retorno. Insira os últimos 4 dígitos.") user_code = st.text_input("Código da chamada") if st.button("Entrar"): if user_code == st.session_state.get("verification"): st.session_state.auth = True st.session_state.phone = phone st.rerun()
Após verificar o código, a conta salva o número do prestador na sessão e, em seguida, o usa como uma chave para pesquisar transações ativas no Bitrix24. Uma conta separada não é necessária aqui: o mesmo número funciona como uma forma de entrar e como um identificador de funcionário para o CRM.
Passo 2. Coletamos o contexto de trabalho do prestador do Bitrix24
Após entrar, a conta coleta o contexto de trabalho em tempo real do CRM. Primeiro, procuramos o prestador no Bitrix24 pelo número de celular. Em seguida, solicitamos todas as transações ativas nas quais esse usuário foi designado como responsável. Depois disso, para cada transação, adicionamos o contato do cliente e seu telefone para coletar cartões na interface a partir desses dados.
python# bitrix_integration.py def get_user_id_by_phone(phone: str): method = "user.search" params = {"FILTER": {"PERSONAL_MOBILE": phone}} resp = requests.post(f"{Config.BITRIX_WEBHOOK}/{method}", json=params, timeout=10) resp.raise_for_status() result = resp.json().get("result", []) return result[0]["ID"] if result else None def get_active_deals(master_phone: str): user_id = get_user_id_by_phone(master_phone) if not user_id: return [] params = { "filter": { "ASSIGNED_BY_ID": user_id, "!STAGE_ID": FINAL_STAGES, }, "select": ["ID", "TITLE", "UF_CRM_ADDRESS", "CONTACT_ID"], } resp = requests.post(f"{Config.BITRIX_WEBHOOK}/crm.deal.list", json=params, timeout=10) resp.raise_for_status() deals = [] for item in resp.json().get("result", []): contact_id = item.get("CONTACT_ID") client_phone = _get_contact_phone(contact_id) if contact_id else None if client_phone: deals.append( { "id": item["ID"], "address": item.get("UF_CRM_ADDRESS", "Endereço não especificado"), "title": item["TITLE"], "client_phone_hidden": client_phone, } ) return deals
Aqui, user_id é o identificador do prestador no Bitrix24, obtido por seu número de telefone. A constante FINAL_STAGES é uma lista dos estágios finais da transação, que excluímos da seleção para que apenas pedidos ativos entrem na conta.
Na saída, o serviço recebe cartões prontos com a transação, endereço e telefone do cliente. O campo client_phone_hidden armazena o número original do cliente do CRM, e não o número intermediário: a substituição ocorre apenas no momento da chamada via Callback API MTS Exolve.
Passo 3. Ligamos por meio de um único número
Quando o prestador clica em "Entrar em contato", o aplicativo chama o método MakeCallback da API MTS Exolve e passa três valores para ele: o número do serviço, o identificador do recurso de retorno e dois lados da chamada: line_1 e line_2. Em seguida, a conversa é coletada no lado da telefonia.
python# exolve_voice.py import requests from config import Config def initiate_masked_call(master_phone: str, client_phone: str): url = "https://api.exolve.ru/voice/v1/Callback" headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"} payload = { "number": Config.SINGLE_SERVICE_NUMBER, "destination": master_phone, "peer": client_phone, "record": True, } try: requests.post(url, headers=headers, json=payload, timeout=5) except Exception as e: print(f"Erro de retorno: {e}")
O botão na conta que inicia essa chamada.
python# app.py if c1.button("📞 Entrar em contato", key=f"call_{deal['id']}"): initiate_masked_call( master_phone=st.session_state.phone, client_phone=deal["client_phone_hidden"], ) st.toast("Conectando... Aguarde a entrada.")
Passo 4. Recebemos o webhook de entrada e procuramos o contexto ativo do cliente
A chamada de retorno do cliente inicia o segundo cenário: agora você precisa entender para quem transferir a chamada. Ao receber uma chamada, o MTS Exolve envia uma solicitação JSON-RPC para nosso servidor com o método getControlCallFollowMe. A partir do parâmetro params.numberA, o serviço pega o número do chamador, procura um contato no Bitrix24 e verifica as transações ativas.
python# webhook_router.py @app.route("/exolve/incoming", methods=["POST"]) def handle_call(): data = request.json or {} caller_phone = data.get("numbers", {}).get("a") master_phone = find_master_phone_by_client(caller_phone)
Encontramos o contato do cliente pelo número de telefone.
pythondef find_master_phone_by_client(client_phone: str): params = { "type": "PHONE", "values": [client_phone], "entity_type": "CONTACT", } resp = requests.post( f"{Config.BITRIX_WEBHOOK}/crm.duplicate.findbycomm", json=params, timeout=10, ) resp.raise_for_status() contacts = resp.json().get("result", {}) if not contacts: return None contact_id = contacts[0]
O ponto de entrada aqui é um: o número do cliente de numbers.a. Por esse número, o serviço levanta o contato e o contexto de trabalho do cliente no CRM.
Passo 5. Roteamos a chamada para o prestador ou para o suporte
Quando o contato é encontrado, o serviço procura a primeira transação ativa desse cliente, pega o responsável dela e, por seu identificador, recebe o número de celular do prestador. Se essa cadeia for totalmente montada, um comando para transferir a chamada para essa pessoa é enviado em resposta ao webhook. Se não houver transação ativa ou o número do responsável não for encontrado, a chamada vai para o número de suporte.
python# webhook_router.py deal_params = { "filter": { "CONTACT_ID": contact_id, "!STAGE_ID": FINAL_STAGES, }, "select": ["ASSIGNED_BY_ID"], } deal_resp = requests.post( f"{Config.BITRIX_WEBHOOK}/crm.deal.list", json=deal_params, timeout=10, ) deal_resp.raise_for_status() deals = deal_resp.json().get("result", []) if not deals: return None assigned_id = deals[0]["ASSIGNED_BY_ID"] user_resp = requests.post( f"{Config.BITRIX_WEBHOOK}/user.get", json={"ID": assigned_id}, timeout=10, )
Encontramos o número de celular do responsável.
pythonuser_resp.raise_for_status() users = user_resp.json().get("result", []) if users: return users[0].get("PERSONAL_MOBILE") return None
Retornamos o comando para transferir a chamada ou removemos para uma rota de backup.
pythonredirect_number = master_phone if master_phone else Config.SUPPORT_NUMBER return jsonify({ "id": req_id, "jsonrpc": "2.0", "sip_id": sip_id, "result": { "redirect_type": 1, "followme_struct": [ 1, [ { "I_FOLLOW_ORDER": 1, "ACTIVE": True, "NAME": "Assigned master", "REDIRECT_NUMBER": redirect_number, "PERIOD": "always", "PERIOD_DESCRIPTION": "always", "TIMEOUT": 30 } ] ] } })
Nesta fase, o Bitrix24 é a fonte da verdade para o roteamento. Não é a telefonia que decide para quem ligar, mas a transação atual no CRM. Isso é conveniente como um MVP, porque você não precisa de seu próprio banco de dados de correspondências entre clientes e funcionários.
Passo 6. Retornamos a alteração de status de volta ao CRM
Após a visita, o prestador pode fechar o pedido da mesma conta. Aqui, o serviço já registra o resultado da execução no CRM. Assim, o cartão da transação e o status real da saída são salvos imediatamente após a chamada ou visita.
python# bitrix_integration.py def closedeal(dealid: str): params = {"id": dealid, "fields": {"STAGEID": "WON"}} requests.post(f"{Config.BITRIX_WEBHOOK}/crm.deal.update", json=params, timeout=10)
O botão na conta que envia essa atualização.
python# app.py if c2.button("✅ Concluído", key=f"done_{deal['id']}"): close_deal(deal["id"]) st.success("Pedido fechado!") st.rerun()
Aqui, o processo é curto: a interface passa o identificador da transação, e o Bitrix24 recebe um novo estágio WON.
Iniciando e verificando
Primeiro, iniciamos o servidor para receber webhooks e, em seguida, a interface do prestador. Em terminais separados, isso se parece com:
bashpython webhook_router.py streamlit run app.py
Para verificar a autorização no SMS_SENDER, especifique o número do remetente registrado, solicite o código na interface e certifique-se de que o SMS chegou ao número de teste.
Depois disso, você pode verificar a rota de entrada manualmente. Se um número de teste for definido em SUPPORT_NUMBER e o Bitrix24 não encontrar o número do cliente como um contato ativo, o serviço deverá fornecer a rota de backup:
bashcurl -X POST "http://127.0.0.1:5000/exolve/incoming" \ -H "Content-Type: application/json" \ -d '{ "id": 1, "jsonrpc": "2.0", "method": "getControlCallFollowMe", "params": { "sip_id": "7800XXXXXXX", "numberA": "79990000000" } }'
A resposta esperada no cenário mínimo:
json{ "id": 1, "jsonrpc": "2.0", "sip_id": "7800XXXXXXX", "result": { "followme_struct": [ 1, [ { "ACTIVE": true, "I_FOLLOW_ORDER": 1, "NAME": "Assigned master", "PERIOD": "always", "PERIOD_DESCRIPTION": "always", "REDIRECT_NUMBER": "7800YYYYYYY", "TIMEOUT": 30 } ] ], "redirect_type": 1 } }
Em seguida, verifique o cenário principal: entre no Streamlit sob o número do prestador, certifique-se de que as transações ativas foram puxadas, clique em Entrar em contato e, em seguida, envie manualmente um webhook com o número do cliente do CRM. Se você recebeu 4xx ou uma rota vazia, primeiro observe a carga útil e os dados no Bitrix24. Se 5xx, verifique os timeouts de rede, a disponibilidade do REST-webhook e a correção dos números nos cartões de prestadores e clientes.
Oportunidades de desenvolvimento
- Levar os números de telefone a um formato unificado na entrada e na saída. Sem isso, a pesquisa no Bitrix24 começa a divergir rapidamente em diferentes formatos de registro
- Adicionar uma assinatura ou outra forma de validar o webhook de entrada. Agora, o endpoint confia em qualquer POST com o JSON apropriado
- Remover os códigos de autorização do session_state para o armazenamento TTL do servidor, adicionar uma restrição na frequência de envio de SMS e um log de tentativas de entrada
- Adicionar novas tentativas com atraso crescente e um identificador de evento de ponta a ponta para solicitações ao MTS Exolve e Bitrix24 para ver onde a cadeia está sendo quebrada
- Verificar as respostas do Bitrix24 e MTS Exolve, registrar falhas e exibi-las na interface para que o fechamento do pedido ou o lançamento da chamada não pareçam bem-sucedidos apenas na tela
- Definir regras de roteamento determinísticas para várias transações ativas do mesmo cliente em vez da regra de pegar a primeira
- Preparar a observabilidade: logs estruturados, alertas sobre erros de integração e uma auditoria simples de eventos por número, transação e funcionário
No final
Resultou em um cenário leve sem seu próprio banco de dados. Ele funciona como um emparelhamento Bitrix24 e MTS Exolve, onde o CRM armazena o contexto de trabalho e a telefonia coleta a conversa em si.
Para um MVP, esta é uma maneira simples de iniciar rapidamente um serviço seguro com um número intermediário e não revelar os números pessoais do prestador e do cliente, e organizar a entrada dos funcionários por meio de um código em SMS sem um sistema de conta separado.
Além disso, esse cenário pode ser desenvolvido por meio do robô de voz MTS Exolve: com IVR e geração de fala, será possível esclarecer o pedido necessário e rotear as chamadas com mais precisão quando vários pedidos estiverem abertos para o cliente ou prestador ao mesmo tempo, mantendo o mesmo número intermediário.
