{
  "openapi": "3.1.0",
  "info": {
    "title": "Integração de Leads do ScaleAds",
    "version": "1.0.0",
    "summary": "Referência técnica do webhook de saída de leads.",
    "description": "# Introdução\n\nO ScaleAds envia leads para o sistema receptor por `POST HTTPS`. O receptor não consulta nem chama uma API do ScaleAds para obter o lead.\n\n```text\nScaleAds ── POST HTTPS ──> sistema receptor\n```\n\nEsta referência cobre o contrato genérico `lead.created` v1 e o perfil específico WebGR. A criação do lead não depende da disponibilidade do receptor: a entrega é persistida em outbox e processada de forma assíncrona.\n\n## Perfis Suportados\n\n- **Genérico:** o receptor implementa integralmente o contrato `lead.created` v1.\n- **WebGR:** o ScaleAds transforma o corpo e interpreta a resposta conforme o contrato do WebGR.\n\nSe a API receptora não corresponder a um perfil existente, a integração permanece bloqueada até um ciclo técnico próprio. Nenhum campo ou comportamento deve ser inventado durante a configuração.\n\n## Semântica de Entrega\n\nA entrega é **at-least-once**. O `X-ScaleAds-Event-Id` e o corpo permanecem estáveis entre tentativas, mas uma falha ambígua pode produzir duplicidade se o receptor não implementar deduplicação.\n\nFalha transitória não encerra a entrega: as tentativas seguem com intervalo crescente, de até 360 minutos entre uma e outra, até o receptor confirmar ou recusar o lead.\n\n## Segurança\n\nTodas as requisições usam HTTPS em porta 443 e recebem assinatura HMAC-SHA256. Credenciais e segredos não são persistidos na URL, no banco de configuração ou nos logs. A assinatura só fornece autenticação ponta a ponta quando o receptor efetivamente a valida.\n\n## O Que Você Precisa Entregar\n\nEsta seção é para quem vai **receber** os leads.\n\n1. Um endpoint HTTPS na porta 443, com certificado válido e alcançável pela internet. O ScaleAds não segue redirecionamento: um `3xx` encerra a entrega em definitivo.\n2. Deduplicação pelo header `X-ScaleAds-Event-Id`. A entrega é at-least-once e o mesmo evento pode chegar mais de uma vez.\n3. Resposta `2xx` assim que o lead for aceito. No contrato genérico o corpo da resposta é ignorado.\n4. Validação da assinatura `X-ScaleAds-Signature`. Confira sua implementação contra o [vetor de teste do HMAC](../exemplos/vetor-hmac-sintetico.json), também disponível no topo desta página, que traz segredo sintético, corpo, timestamp e a assinatura esperada, sem envolver segredo real.\n5. Devolva ao ScaleAds a URL do endpoint e um segredo para a assinatura, por canal seguro.\n\nEnquanto a URL e o segredo não chegarem, não há para onde enviar: não existe autosserviço nem chave a ser obtida nesta página.\n\n## Arquivos de Máquina\n\n[`openapi.json`](../openapi.json) (esta referência), [`lead-created-v1.schema.json`](../lead-created-v1.schema.json), [`perfis/webgr-payload.schema.json`](../perfis/webgr-payload.schema.json) e [`exemplos/vetor-hmac-sintetico.json`](../exemplos/vetor-hmac-sintetico.json)."
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "paths": {},
  "tags": [
    {
      "name": "Contrato Genérico",
      "description": "Evento `lead.created` v1 enviado para um endpoint configurável do mesmo tenant."
    },
    {
      "name": "Perfil WebGR",
      "description": "Formato e resposta próprios do destino WebGR, sem alterar o motor de entrega."
    }
  ],
  "webhooks": {
    "leadCreatedGeneric": {
      "post": {
        "tags": [
          "Contrato Genérico"
        ],
        "summary": "Lead Criado no Contrato Genérico",
        "description": "O ScaleAds envia este evento ao endpoint HTTPS configurado para o tenant. Todos os campos do objeto `lead` aparecem no JSON; valor ausente é `null`. O corpo fica congelado no momento do claim e é repetido byte a byte em novas tentativas.",
        "operationId": "receiveLeadCreatedV1",
        "security": [
          {
            "ScaleAdsHmac": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XScaleAdsEvent"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsEventId"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsTimestamp"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsSignature"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "./lead-created-v1.schema.json"
              },
              "examples": {
                "leadCreated": {
                  "summary": "Lead Sintético",
                  "value": {
                    "event": "lead.created",
                    "version": 1,
                    "event_id": "lead-created-900001",
                    "occurred_at": "2026-08-17T18:30:00-03:00",
                    "lead": {
                      "id": 900001,
                      "name": "Contato de Teste",
                      "phone": null,
                      "source": "formulario_teste",
                      "utm_source": "documentacao",
                      "utm_medium": null,
                      "utm_campaign": null,
                      "utm_content": null,
                      "utm_term": null,
                      "gclid": null,
                      "fbclid": null
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Entrega confirmada. O corpo da resposta é ignorado."
          },
          "3XX": {
            "description": "Falha definitiva. O ScaleAds não segue redirecionamentos."
          },
          "408": {
            "description": "Falha transitória; a entrega é tentada novamente."
          },
          "429": {
            "description": "Falha transitória. `Retry-After` é respeitado, limitado a 360 minutos."
          },
          "4XX": {
            "description": "Falha definitiva, exceto `408` e `429`."
          },
          "5XX": {
            "description": "Falha transitória; a entrega é tentada novamente. Em `503`, o `Retry-After` é respeitado, limitado a 360 minutos."
          }
        }
      }
    },
    "leadCreatedWebgr": {
      "post": {
        "tags": [
          "Perfil WebGR"
        ],
        "summary": "Lead Criado no Perfil WebGR",
        "description": "O ScaleAds envia exatamente um lead para `https://integracoes.webgrapp.com.br/api/leads/importar/webgr/{company_code}`. Campos sem valor válido são omitidos e nenhum dado é fabricado. A URL persistida não contém query; `username` e `password` são anexados somente em memória no instante do envio.",
        "operationId": "receiveLeadCreatedWebgr",
        "externalDocs": {
          "description": "Documentação Pública do Receptor WebGR",
          "url": "https://documenter.getpostman.com/view/32499757/2sAYdZuZuA"
        },
        "security": [
          {
            "ScaleAdsHmac": [],
            "WebGRUsername": [],
            "WebGRPassword": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/XScaleAdsEvent"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsEventId"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsTimestamp"
          },
          {
            "$ref": "#/components/parameters/XScaleAdsSignature"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "./perfis/webgr-payload.schema.json"
              },
              "examples": {
                "webgrLead": {
                  "summary": "Lead Sintético com os Dados Disponíveis",
                  "value": {
                    "leads": [
                      {
                        "company_name": "Empresa de Teste Ltda",
                        "company_nick": "Empresa de Teste",
                        "contact": "Contato de Teste",
                        "origin_code": "ORIGEM_ACORDADA",
                        "observation": "origem=formulario_teste | origem_sistema=ScaleAds | lead_id=900001 | event_id=lead-created-900001"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Confirma a entrega somente com HTTP exatamente 200, corpo JSON válido, `status` interno inteiro igual a 200 e `errors` exatamente `null`. Outro 2xx (201, 204), `status` como texto ou ausente e `errors` como array vazio, string vazia ou `false` não confirmam: a entrega segue como transitória e é tentada novamente.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebGRResponse"
                },
                "examples": {
                  "success": {
                    "summary": "Resposta Publicada de Sucesso",
                    "value": {
                      "status": 200,
                      "response": {
                        "message": "Transação efetuada com sucesso"
                      },
                      "errors": null
                    }
                  }
                }
              }
            }
          },
          "3XX": {
            "description": "Falha definitiva. O ScaleAds não segue redirecionamentos."
          },
          "401": {
            "description": "Falha transitória: credencial recusada pelo WebGR. O time do ScaleAds é avisado e a entrega é tentada novamente até a credencial ser corrigida."
          },
          "403": {
            "description": "Falha transitória: credencial recusada pelo WebGR. O time do ScaleAds é avisado e a entrega é tentada novamente até a credencial ser corrigida."
          },
          "408": {
            "description": "Falha transitória; a entrega é tentada novamente."
          },
          "429": {
            "description": "Falha transitória. `Retry-After` é respeitado, limitado a 360 minutos."
          },
          "4XX": {
            "description": "Falha definitiva, exceto `401`, `403`, `408` e `429`."
          },
          "5XX": {
            "description": "Falha transitória; a entrega é tentada novamente. Em `503`, o `Retry-After` é respeitado, limitado a 360 minutos."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ScaleAdsHmac": {
        "type": "apiKey",
        "in": "header",
        "name": "X-ScaleAds-Signature",
        "description": "HMAC-SHA256 sobre `<timestamp>.<corpo exato em bytes>`, no formato `t=<timestamp>, v1=<hex>`. O receptor também deve conferir `X-ScaleAds-Timestamp`, aplicar sua janela contra replay e comparar em tempo constante."
      },
      "WebGRUsername": {
        "type": "apiKey",
        "in": "query",
        "name": "username",
        "description": "Nome da credencial exigida pelo WebGR. O valor existe somente no armazenamento seguro e na memória do envio."
      },
      "WebGRPassword": {
        "type": "apiKey",
        "in": "query",
        "name": "password",
        "description": "Nome da credencial exigida pelo WebGR. O valor existe somente no armazenamento seguro e na memória do envio."
      }
    },
    "parameters": {
      "XScaleAdsEvent": {
        "name": "X-ScaleAds-Event",
        "in": "header",
        "required": true,
        "description": "Nome estável do evento.",
        "schema": {
          "type": "string",
          "const": "lead.created"
        },
        "example": "lead.created"
      },
      "XScaleAdsEventId": {
        "name": "X-ScaleAds-Event-Id",
        "in": "header",
        "required": true,
        "description": "Identificador estável entre tentativas e chave de deduplicação do receptor.",
        "schema": {
          "type": "string",
          "pattern": "^lead-created-[1-9][0-9]*$"
        },
        "example": "lead-created-900001"
      },
      "XScaleAdsTimestamp": {
        "name": "X-ScaleAds-Timestamp",
        "in": "header",
        "required": true,
        "description": "Epoch Unix em segundos gerado para a tentativa atual.",
        "schema": {
          "type": "integer",
          "format": "int64",
          "minimum": 1
        },
        "example": 1787000000
      },
      "XScaleAdsSignature": {
        "name": "X-ScaleAds-Signature",
        "in": "header",
        "required": true,
        "description": "Assinatura HMAC no formato `t=<timestamp>, v1=<64 caracteres hexadecimais>`.",
        "schema": {
          "type": "string",
          "pattern": "^t=[1-9][0-9]*, v1=[0-9a-f]{64}$"
        },
        "example": "t=1787000000, v1=48c143d1c85c6b7375a25dfcb936eaa4ae95a91e1f8f24c3a270192219d24c19"
      }
    },
    "schemas": {
      "WebGRResponse": {
        "type": "object",
        "required": [
          "status",
          "errors"
        ],
        "properties": {
          "status": {
            "description": "Status interno do receptor, que não se confunde com o código HTTP da resposta. O inteiro 200 é uma das condições necessárias para confirmar a entrega. Um inteiro diferente de 200, inclusive 201 e 204, é recusa explícita do receptor: a entrega é encerrada em definitivo e não há nova tentativa. Campo ausente ou de tipo inválido, como a string \"200\", é resposta ambígua e a entrega é tentada de novo. Um HTTP 2xx que não seja 200 também é tratado como transitório, qualquer que seja o valor deste campo.",
            "type": "integer",
            "const": 200
          },
          "response": {
            "description": "Conteúdo informativo definido pelo receptor.",
            "type": [
              "object",
              "array",
              "string",
              "number",
              "boolean",
              "null"
            ]
          },
          "errors": {
            "description": "A entrega só é confirmada quando este campo é exatamente `null`. `false`, array vazio e string vazia não confirmam. Valor preenchido representa rejeição definitiva.",
            "type": "null"
          }
        },
        "additionalProperties": true
      }
    }
  }
}
