Back to site
Syncro

Webhooks

Subscribe to events and receive real-time notifications at your URL, with every delivery signed via HMAC.

Base URLhttps://app.syncro.chat/api/v1AuthX-API-Key: crm_SUA_CHAVE_AQUI

List subscriptions

GET/webhooks
Permission: webhooks:read

Lists the account webhook subscriptions.

Request
curl "https://app.syncro.chat/api/v1/webhooks" \
  -H "X-API-Key: crm_SUA_CHAVE_AQUI"
Response
{
  "success": true,
  "data": [
    {
      "id": 1,
      "target_url": "https://seu-sistema.com/webhooks/syncro",
      "events": [
        "lead.created",
        "lead.updated"
      ],
      "source": "api",
      "is_active": true,
      "failure_count": 0,
      "last_success_at": "2026-06-30T10:05:00Z",
      "last_failure_at": null,
      "created_at": "2026-06-15T14:30:00Z"
    }
  ],
  "supported_events": [
    "lead.created",
    "lead.updated",
    "lead.stage_changed",
    "..."
  ]
}

Create subscription

POST/webhooks
Permission: webhooks:write

Creates a subscription pointing to your URL and chooses which events to receive.

Body parameters

target_urlstring (URL)required
URL that will receive the POST requests
eventsarray(string)required
Events to subscribe to (see list)
sourcestringoptional
api (default) or n8n
secretstringoptional
HMAC secret. If omitted, Syncro **generates one** and returns it
i

Save the secret: you will use it to validate the signature of each delivery.

Request
curl -X POST "https://app.syncro.chat/api/v1/webhooks" \
  -H "X-API-Key: crm_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "target_url": "https://seu-sistema.com/webhooks/syncro",
    "events": [
      "lead.created",
      "lead.won",
      "task.completed"
    ]
  }'
Response
{
  "success": true,
  "id": 1,
  "secret": "f3a9c2...e1",
  "events": [
    "lead.created",
    "lead.won",
    "task.completed"
  ]
}

Subscription detail

GET/webhooks/1
Permission: webhooks:read

Returns the data of a specific subscription.

Request
curl "https://app.syncro.chat/api/v1/webhooks/1" \
  -H "X-API-Key: crm_SUA_CHAVE_AQUI"
Response
{
  "success": true,
  "id": 1,
  "target_url": "https://seu-sistema.com/webhooks/syncro",
  "events": [
    "lead.created",
    "lead.updated"
  ],
  "source": "api",
  "is_active": true,
  "failure_count": 0,
  "last_success_at": "2026-06-30T10:05:00Z",
  "last_failure_at": null,
  "last_failure_reason": null
}

Update subscription

PUT/webhooks/1
Permission: webhooks:write

Update the URL, the events or activate/deactivate. Reactivating (is_active: true) resets the failure counter.

Parameters (body, all optional)

target_urlstring (URL)optional
New URL
eventsarray(string)optional
New list of events
is_activebooleanoptional
Activates/deactivates
Request
curl -X PUT "https://app.syncro.chat/api/v1/webhooks/1" \
  -H "X-API-Key: crm_SUA_CHAVE_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      "lead.created"
    ],
    "is_active": true
  }'
Response
{
  "success": true,
  "id": 1,
  "events": [
    "lead.created"
  ],
  "is_active": true
}

Delete subscription

DELETE/webhooks/1
Permission: webhooks:write

Removes a webhook subscription.

Request
curl -X DELETE "https://app.syncro.chat/api/v1/webhooks/1" \
  -H "X-API-Key: crm_SUA_CHAVE_AQUI"
Response
{
  "success": true
}

How delivery works

When a subscribed event occurs, Syncro makes a POST (JSON) to your target_url.

Envelope

Every event arrives in this format:

{
  "event": "lead.created",
  "tenant_id": 1,
  "emitted_at": "2026-06-30T10:00:00Z",
  "data": {
    "...": "payload específico do evento"
  }
}

Delivery headers

Header Value
Content-Type application/json
User-Agent Syncro-CRM-Webhook/1.0
X-Syncro-Event event name (e.g.: lead.created)
X-Syncro-Delivery unique delivery ID
X-Syncro-Signature sha256=<hmac> — HMAC signature of the body

Validating the signature (HMAC)

The signature is the HMAC-SHA256 of the raw body of the request, using the subscription secret as the key. The header comes as sha256=<hexadecimal_hash>.

Compute the HMAC over the raw body received (do not re-serialize the JSON) and compare with the value after sha256=.

Node.js (Express)

const crypto = require('crypto');

// use o corpo BRUTO: app.use(express.raw({ type: 'application/json' }))
function verify(req, secret) {
  const header = req.get('X-Syncro-Signature') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.body) // Buffer com o corpo bruto
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

PHP

$raw = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $raw, $secret);
$ok = hash_equals($expected, $_SERVER['HTTP_X_SYNCRO_SIGNATURE'] ?? '');

Respond with 2xx to confirm receipt. Any other status is treated as a failure.

Redeliveries and retry

  • Syncro tries to deliver up to 4 times, with increasing wait: 10s, 1min, 5min, 15min.
  • Responses 5xx, 408 and 429 are resent. Responses 4xx (except 408/429) are not resent (receiver error).
  • After 10 consecutive failures, the subscription is automatically deactivated (reactivate with PUT /webhooks/{id} and is_active: true).
  • Make your endpoint idempotent (it may receive the same delivery more than once); use the X-Syncro-Delivery to deduplicate.

Available events

Event When it fires
lead.created Lead created
lead.updated Lead updated
lead.stage_changed Lead changed stage
lead.won Lead marked as won
lead.lost Lead marked as lost
lead.deleted Lead deleted
lead.assignment_changed Lead owner changed
lead.merged Leads merged
lead.tags_changed Lead tags changed
sale.created Sale registered
note.added Note added to a lead
nps.answered NPS survey answered
task.created Task created
task.updated Task updated
task.completed Task completed
conversation.message_received Reserved (not yet emitted)

Payload examples

Examples of the data field:

lead.created / lead.updated / lead.deleted

{
  "id": 123,
  "name": "João Silva",
  "email": "[email protected]",
  "phone": "+5511999887766",
  "company": "ACME",
  "value": 5000.0,
  "source": "site",
  "status": "open",
  "pipeline_id": 1,
  "pipeline_name": "Vendas",
  "stage_id": 5,
  "stage_name": "Proposta",
  "assigned_to": 12,
  "owner": { "id": 12, "name": "Ana", "email": "[email protected]" },
  "tags": ["vip"],
  "utm_source": "google",
  "utm_medium": "cpc",
  "utm_campaign": "verao_2026",
  "utm_term": null,
  "utm_content": null,
  "fbclid": null,
  "gclid": null,
  "custom_fields": { "tamanho_empresa": "100+" },
  "created_at": "2026-06-30T10:00:00Z",
  "updated_at": "2026-06-30T10:00:00Z"
}

lead.stage_changed / lead.won / lead.lost — the same lead object, plus:

{
  "previous_stage_id": 4,
  "new_stage_id": 5,
  "new_stage_name": "Proposta"
}

sale.created

{
  "sale_id": 1,
  "lead_id": 123,
  "pipeline_id": 1,
  "value": 5000.0,
  "closed_by": 12,
  "closed_at": "2026-06-30T10:05:00Z",
  "lead": {
    "id": 123,
    "name": "João Silva",
    "email": "[email protected]",
    "phone": "+5511999887766",
    "company": "ACME",
    "pipeline_id": 1,
    "stage_id": 10,
    "owner": { "id": 12, "name": "Ana", "email": "[email protected]" }
  }
}

task.created / task.updated / task.completed

{
  "id": 45,
  "subject": "Enviar proposta",
  "type": "email",
  "status": "completed",
  "priority": "high",
  "due_date": "2026-07-01",
  "due_time": "09:00",
  "completed_at": "2026-06-30T11:30:00Z",
  "lead_id": 123,
  "assigned_to": 12,
  "created_at": "2026-06-30T08:00:00Z",
  "updated_at": "2026-06-30T11:30:00Z"
}

note.added, nps.answered, lead.assignment_changed, lead.merged and lead.tags_changed follow the same envelope, with the event-specific data (including the context lead when applicable).