# Webhooks

## Porque usar *webhooks*?

Ao desenvolver integrações com a Autentique, pode ser interessante que suas aplicações recebam eventos conforme eles ocorrem nas suas organizações.

Para começar a receber *webhooks*, é necessário registrar seus *endpoints* no painel.  Após o registro, o Autentique pode enviar dados de eventos em tempo real para seus *endpoints* de *webhook*  sempre que ocorrerem eventos na sua organização. O Autentique utiliza HTTPS para enviar esses eventos como um *payload* JSON que inclui um objeto *Event*.

Receber eventos de *webhook* é especialmente útil para acompanhar eventos assíncronos, como quando um signatário assina um documento, um documento é completado, ou quando ações relacionadas ao processamento de documentos são concluídas.

### Objeto do Evento

O evento a seguir mostra um \`update\` no nome de um documento.

{% code overflow="wrap" lineNumbers="true" fullWidth="false" %}

```json
{
  "id": "MXwyMWZiY2VjOS1lMWI1LTRkY2EtYWZiYi0wMjIwNjFlOWVhODg=",
  "object": "webhook",
  "name": "endpoint de teste 2",
  "format": "json",
  "url": "https://essa-url-nao-existe.autentique.com.br/webhooks",
  "event": {
    "id": "21fbcec9-e1b5-4dca-afbb-022061e9ea88",
    "object": "event",
    "organization": 1,
    "type": "document.updated",
    "data": {
      "object": {
        "id": "89c7d2ab31f9f5a13b3d20ecf53319af387e54d240ae7be993",
        "name": "Nome atualizado",
        "refusable": true,
        "stop_on_rejected": true,
        "qualified": false,
        "ignore_cpf": true,
        "sortable": false,
        "is_blocked": false,
        "sandbox": 0,
        "scrolling_required": 0,
        "locale": {
          "country": "BR",
          "language": "pt-BR",
          "timezone": "America/Sao_Paulo",
          "date_format": "d/m/Y"
        },
        "created_at": "2024-08-26T18:02:26.000000Z",
        "updated_at": "2024-08-26T18:03:27.000000Z",
        "deleted_at": null,
        "deadline_at": null,
        "lifecycle_in": "2029-08-26T03:00:00.000000Z",
        "email_template_id": null,
        "expiration_at": null,
        "notify_in": null,
        "reminder": null,
        "message": "Mudei essa mensagem também",
        "reply_to": null,
        "signatures_count": 1,
        "signed_count": 0,
        "rejected_count": 0,
        "object": "document",
        "is_from_api": false,
        "signatures": [
          {
            "public_id": "7f25d72b-6155-11ef-9dae-0242ac170004",
            "name": "Felipe Autentique",
            "company": null,
            "email": "felipe@autentique.com.br",
            "phone": null,
            "cpf": "123.456.789-09",
            "birthday": "1979-08-13",
            "action": "Sign",
            "viewed": "2024-08-26T18:02:27.000000Z",
            "signed": null,
            "rejected": null,
            "validation_unapproved": null,
            "validation_approved": null,
            "validation_rejected": null,
            "created_at": "2024-08-26T18:02:26.000000Z"
          }
        ],
        "author": {
          "name": "Felipe Autentique",
          "company": null,
          "email": "felipe@autentique.com.br",
          "phone": null,
          "cpf": "123.456.789-09",
          "birthday": "1979-08-13"
        },
        "files": {
          "original": "https://storage.googleapis.com/f77/6e7a1fadeed9c56cf037b43a9cd0e6d1/o9baEgHHv1Tuf7AZttDqdL8vK68eUw56PaYXzRqV.original.pdf",
          "signed": "https://painel.autentique.com.br/documentos/89c7d2ab31f95a13b3d20ecf53319af387e54d240ae7be993/assinado.pdf",
          "certified": "https://painel.autentique.com.br/documentos/89c7d2ab31f95a13b3d20ecf53319af387e54d240ae7be993/certificado.pdf",
          "pades": "https://painel.autentique.com.br/documentos/89c7d2ab31f95a13b3d20ecf53319af387e54d240ae7be993/pades.pdf"
        }
      },
      "previous_attributes": {
        "name": "teste",
        "refusable": false,
        "updated_at": "2024-08-26T18:02:26.000000Z",
        "message": "Por favor acesse e assine eletronicamente o documento clicando no botão acima.",
        "ignore_cpf": false
      }
    },
    "created_at": "2024-08-26T18:03:27.387179Z"
  }
}
```

{% endcode %}

#### Tipo do Evento

Todos os webhooks enviados são referentes à um evento de determinado recurso. Esse é informado pelo campo `type`. Da mesma forma, o campo `data.object` corresponde ao recurso do evento.

#### Objeto `data` e `previous_attributes`

Para os eventos `*.updated`, o *payload* do evento inclui o campo `data.previous_attributes` que permite que você inspecione o que mudou no referente recurso. No evento `document.updated` usado como exemplo acima, é indicado que o documento possuía o nome `"teste"` anteriormente.

## Como Começar

1. Adicione seu Endpoint no Painel de desenvolvedor do Autentique, selecionando os eventos que deseja escutar.
2. Configure uma função de HTTPS que aceite solicitações de webhooks com um método POST.
   1. Processe solicitações POST com um conteúdo JSON que consiste em um [objeto do evento](#objeto-do-evento).
   2. Retorne rapidamente um código de status de êxito (`2xx`) antes de qualquer lógica complexa que possa esgotar um tempo limite. Por exemplo, você precisa retornar uma resposta `200` antes de atualizar os dados do document no seu sistema.
   3. Caso deseje, você pode [verificar se eventos são enviados pelo Autentique](#verify-events), antes de fazer qualquer manipulação com o payload.

#### Exemplo de endpoint:

{% tabs %}
{% tab title="PHP" %}

```php
$payload = @file_get_contents('php://input');
$webhook = null;

//Antes de processar o payloa

try {
    $webhook = json_decode($payload, true);
} catch(\UnexpectedValueException $e) {
    // Invalid payload
    http_response_code(400);
    exit();
}

$event = $webhook['event'];

// processa o event
switch ($event['type']) {
    case 'document.created':
        $document = $event['data']['object']; // Contém um objeto de documento
        // Então defina um método para processar esse documento
        // handleDocumentCreated($document);
        break;
    case 'signature.accepted':
        $signature = $event['data']['object']; // contém um objeto de assinatura
        // Então defina um método para processar essa assinatura
        // handleSignatureAccepted($signature);
        break;
        // ... processe outros tipos de evento
    default:
        echo 'Received unknown event type '.$event['type'];
}

http_response_code(200);
```

{% endtab %}

{% tab title="Go" %}

```go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type WebhookPayload struct {
	ID     string `json:"id"`
	Object string `json:"object"`
	Name   string `json:"name"`
	Format string `json:"format"`
	URL    string `json:"url"`
	Event  Event  `json:"event"`
}

type Event struct {
	ID           string                 `json:"id"`
	Object       string                 `json:"object"`
	Organization int                    `json:"organization"`
	Type         string                 `json:"type"`
	Data         map[string]interface{} `json:"data"`
	CreatedAt    string                 `json:"created_at"`
}

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	payload, err := ioutil.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "Invalid payload", http.StatusBadRequest)
		return
	}

	var webhook WebhookPayload
	if err := json.Unmarshal(payload, &webhook); err != nil {
		http.Error(w, "Invalid payload", http.StatusBadRequest)
		return
	}

	event := webhook.Event
	eventData := event.Data
	eventObject := eventData["object"].(map[string]interface{})

	switch event.Type {
	case "document.created":
		// handleDocumentCreated(eventObject)
		fmt.Println("Document created:", eventObject)
	case "document.updated":
		// handleDocumentUpdated(eventObject)
		fmt.Println("Document updated:", eventObject)
	case "signature.accepted":
		// handleSignatureAccepted(eventObject)
		fmt.Println("Signature accepted:", eventObject)
	default:
		fmt.Printf("Received unknown event type: %s\n", event.Type)
	}

	w.WriteHeader(http.StatusOK)
}

func main() {
	http.HandleFunc("/webhook", webhookHandler)
	http.ListenAndServe(":8080", nil)
}
```

{% endtab %}

{% tab title="Java" %}

```java
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;

import java.util.Map;

@RestController
@RequestMapping("/webhook")
public class WebhookController {

    @PostMapping
    public ResponseEntity<String> handleWebhook(@RequestBody Map<String, Object> payload) {
        try {
            Map<String, Object> event = (Map<String, Object>) payload.get("event");
            String eventType = (String) event.get("type");
            Map<String, Object> data = (Map<String, Object>) event.get("data");
            Map<String, Object> eventObject = (Map<String, Object>) data.get("object");

            switch (eventType) {
                case "document.created":
                    // handleDocumentCreated(eventObject);
                    System.out.println("Document created: " + eventObject);
                    break;
                case "document.updated":
                    // handleDocumentUpdated(eventObject);
                    System.out.println("Document updated: " + eventObject);
                    break;
                case "signature.accepted":
                    // handleSignatureAccepted(eventObject);
                    System.out.println("Signature accepted: " + eventObject);
                    break;
                default:
                    System.out.println("Received unknown event type: " + eventType);
            }

            return new ResponseEntity<>("Webhook received", HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>("Invalid payload", HttpStatus.BAD_REQUEST);
        }
    }
}
```

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

app.post('/webhook', (req, res) => {
    try {
        const payload = req.body;
        const event = payload.event;
        const eventType = event.type;
        const eventData = event.data;
        const eventObject = eventData.object;

        switch (eventType) {
            case 'document.created':
                // handleDocumentCreated(eventObject);
                console.log("Document created:", eventObject);
                break;
            case 'document.updated':
                // handleDocumentUpdated(eventObject);
                console.log("Document updated:", eventObject);
                break;
            case 'signature.accepted':
                // handleSignatureAccepted(eventObject);
                console.log("Signature accepted:", eventObject);
                break;
            default:
                console.log(`Received unknown event type: ${eventType}`);
        }

        res.status(200).send('Webhook received');
    } catch (error) {
        res.status(400).send('Invalid payload');
    }
});

app.listen(8080, () => {
    console.log('Server is running on port 8080');
});
```

{% endtab %}

{% tab title="Python" %}

```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    try:
        payload = request.json
        event = payload['event']
        event_type = event['type']
        event_data = event['data']
        event_object = event_data['object']

        if event_type == 'document.created':
            # handle_document_created(event_object)
            print("Document created:", event_object)
        elif event_type == 'document.updated':
            # handle_document_updated(event_object)
            print("Document updated:", event_object)
        elif event_type == 'signature.accepted':
            # handle_signature_accepted(event_object)
            print("Signature accepted:", event_object)
        else:
            print(f"Received unknown event type: {event_type}")

        return jsonify({"status": "success"}), 200
    except Exception as e:
        return jsonify({"error": "Invalid payload"}), 400

if __name__ == '__main__':
    app.run(port=8080)
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
class WebhooksController < ApplicationController
    skip_before_action :verify_authenticity_token

    def webhook
        begin
            payload = JSON.parse(request.body.read)
            event = payload['event']
            event_type = event['type']
            event_data = event['data']
            event_object = event_data['object']

            case event_type
            when 'document.created'
                # handle_document_created(event_object)
                puts "Document created: #{event_object}"
            when 'document.updated'
                # handle_document_updated(event_object)
                puts "Document updated: #{event_object}"
            when 'signature.accepted'
                # handle_signature_accepted(event_object)
                puts "Signature accepted: #{event_object}"
            else
                puts "Received unknown event type: #{event_type}"
            end

            render json: { status: 'success' }, status: :ok
        rescue => e
            render json: { error: 'Invalid payload' }, status: :bad_request
        end
    end
end
```

{% endtab %}
{% endtabs %}

### Ordem de eventos <a href="#event-ordering" id="event-ordering"></a>

O Autentique não garante a entrega dos eventos na ordem em que foram gerados. Por exemplo, a criação de um documento pode gerar os seguintes eventos:

* `document.updated`
* `document.created`
* `signature.created`
* `signature.viewed` (se o autor for um signatário ao criar o documento)

Seu endpoint não deve esperar a entrega desses eventos nesta ordem e deve processá-la de acordo. Você também pode usar a API para recuperar objetos ausentes (por exemplo, você pode obter as assinaturas, pastas e organizações usando as informações de `document.created` se receber esse evento primeiro).

### Práticas Recomendadas para Uso de *Webhooks*

Revise essas práticas recomendadas para garantir que seus *webhooks* permaneçam seguros e funcionem bem com sua integração.

#### Retornar rapidamente uma resposta 2xx <a href="#acknowledge-events-immediately" id="acknowledge-events-immediately"></a>

O endpoint precisa retornar rapidamente um código de status de êxito (`2xx`) antes de qualquer lógica complexa que possa esgotar um tempo limite. Por exemplo, ao receber um `document.finished` você precisa retornar uma resposta `200` antes de atualizar os dados do documento no seu sistema.

#### Gerencie Eventos de Forma Assíncrona

Configure o gerenciador para processar eventos recebidos com uma fila assíncrona. Processar eventos de forma síncrona pode causar problemas de escalabilidade, especialmente durante picos de entregas de *webhook.*

* **Uso de Filas:** Utilize filas assíncronas para processar eventos simultâneos a uma taxa que seu sistema consiga suportar.

#### Gerenciar Eventos Duplicados

Ocasionalmente, os endpoints de *webhook* podem receber o mesmo evento mais de uma vez. Para se proteger contra recibos de eventos duplicados:

* **Registre os IDs de Evento:** Armazene os IDs dos eventos que você processou e ignore eventos já registrados.
* **Identificação de Duplicatas:** Em alguns casos, dois *webhooks* do mesmo evento podem ser enviados para seu endpoint, use o ID do objeto em `event.data` junto com o `event.type`.

#### Ouça Apenas os Tipos de Eventos Necessários

Configure seus endpoints de *webhook* para receber somente os tipos de eventos exigidos pela sua integração. Escutar eventos adicionais ou todos os eventos pode sobrecarregar seu servidor e não é recomendável.

* **Configuração de Eventos:** Você pode alterar os eventos que um endpoint de *webhook* recebe no Painel.

#### Rota de Webhook Isenta de Proteção contra CSRF

Se estiver usando frameworks como **Rails**, **Django** ou **Laravel**, seu site pode verificar automaticamente se cada solicitação POST contém um token CSRF. Esse é um recurso de segurança importante que protege contra tentativas de falsificação de solicitações entre sites, mas pode impedir que seu site processe eventos legítimos de *webhooks*. Para resolver isso, você pode isentar a rota dos *webhooks* da proteção contra CSRF.

{% tabs %}
{% tab title="Laravel" %}

```php
<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;

# No Laravel, 
# você pode adicionar a rota de webhook como exceção 
# middleware VerifyCsrfToken no arquivo `bootstrap/app.php`
return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        channels: __DIR__.'/../routes/channels.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->validateCsrfTokens(except: [
            'webhooks/*' // <-- remove a rota da validação
        ]);
    })->create();
```

{% endtab %}

{% tab title="Django" %}

```python
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse

# No Django, você pode isentar uma view específica 
# da verificação CSRF sando o decorator @csrf_exempt
@csrf_exempt
@require_POST
def webhook_endpoint(request):
    # Processar o webhook
```

{% endtab %}

{% tab title="Rails" %}

```ruby
# No Rails, você pode isentar uma ação específica do protect_from_forgery
class WebhooksController < ApplicationController
  protect_from_forgery except: :webhook

  def webhook
    # Process webhook data in `params`
  end

end
```

{% endtab %}
{% endtabs %}

#### Verificar se eventos são enviados pelo Autentique <a href="#verify-events" id="verify-events"></a>

Para garantir que os eventos recebidos realmente provêm do Autentique, é fundamental validar as assinaturas HMAC presentes nos cabeçalhos dos webhooks.  A seguir, alguns exemplos de como fazer isso:&#x20;

{% tabs %}
{% tab title="PHP" %}

```php
public function verifySignature(array $headers, string $payload, string $secret): bool
{
    if (!isset($headers['x-autentique-signature'])) {
        return false;
    }

    $signature = $headers['x-autentique-signature'];
    $calculatedSignature = hash_hmac('sha256', $payload, $secret);

    return hash_equals($calculatedSignature, $signature);
}
```

{% endtab %}

{% tab title="Node" %}

```javascript
const crypto = require('crypto');

function verifySignature(headers, rawBody, secret) {
  const signature = headers["x-autentique-signature"];
  if (!signature) {
    return false;
  }
  const calculatedSignature = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(calculatedSignature, "hex"),
    Buffer.from(signature, "hex"),
  );
}
```

{% endtab %}

{% tab title="Python" %}

```python
import hmac
import hashlib
import json

def verify_signature(headers, payload, secret):
    signature = headers.get('X-Autentique-Signature')
    if not signature:
        return False
    payload_json = json.dumps(payload, separators=(',', ':')).encode('utf-8')
    calculated_signature = hmac.new(secret.encode('utf-8'), payload_json, hashlib.sha256).hexdigest()
    return hmac.compare_digest(calculated_signature, signature)
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'json'
require 'openssl'
require 'active_support/security_utils'

def verify_signature(headers, payload, secret)
  signature = headers['X-Autentique-Signature']
  return false unless signature
  payload_json = payload.to_json
  calculated_signature = OpenSSL::HMAC.hexdigest('SHA256', secret, payload_json)
  ActiveSupport::SecurityUtils.secure_compare(calculated_signature, signature)
end
```

{% endtab %}

{% tab title="Java" %}

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class WebhookVerifier {
    private String secret;

    public WebhookVerifier(String secret) {
        this.secret = secret;
    }

    public boolean verifySignature(String headerSignature, String payload) throws Exception {
        String calculatedSignature = calculateSignature(payload);
        return MessageDigest.isEqual(calculatedSignature.getBytes(StandardCharsets.UTF_8),
                                     headerSignature.getBytes(StandardCharsets.UTF_8));
    }

    private String calculateSignature(String payload) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] hash = sha256_HMAC.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(hash);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder(2 * bytes.length);
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if(hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
}

```

{% endtab %}
{% endtabs %}

### Objeto de Documento

```json
{
  "id": "MXwwNmMzOGYyMS0zNjhjLTQyNmItOTM2Ny1iMzNhNzQ2NmY5MGM=",
  "object": "webhook",
  "name": "docs",
  "format": "json",
  "url": "https://essa-url-nao-existe.autentique.com.br/webhooks",
  "event": {
    "id": "06c38f21-368c-426b-9367-b33a7466f90c",
    "object": "event",
    "organization": 1,
    "type": "document.updated",
    "data": {
      "id": "1cf7d351a96696fdf450ba893f6720463599dd8c34e0aeda803d",
      "object": "document",
      "name": "teste sddd",
      "message": "Por favor acesse e assine eletronicamente o documento clicando no botão acima.",
      "refusable": true,
      "author": {
        "name": "Felipe Autentique",
        "company": null,
        "email": "felipe@autentique.com.br",
        "phone": null,
        "cpf": "03351152094",
        "birthday": "2005-01-24"
      },
      "signatures": [
        {
          "public_id": "92b49dbe-df08-11ef-903c-0242ac140004",
          "object": "signature",
          "user": {
            "name": "Felipe Autentique",
            "company": null,
            "email": "felipe@autentique.com.br",
            "phone": null,
            "cpf": "03351152094",
            "birthday": "2005-01-24"
          },
          "document": "1cf7d351a96696450ba893f6720463599dd8c34e0aeda803d",
          "action": "Sign",
          "viewed": "2025-01-30T12:49:01.000000Z",
          "signed": null,
          "rejected": null,
          "biometric_unapproved": null,
          "biometric_approved": null,
          "biometric_rejected": null,
          "events": [],
          "created_at": "2025-01-30T12:48:58.000000Z"
        },
        {
          "public_id": "93115640-df08-11ef-903c-0242ac140004",
          "object": "signature",
          "user": {
            "name": "Mateus Autentique",
            "company": null,
            "email": "mateus@autentique.com.br",
            "phone": null,
            "cpf": "189.614.966-03",
            "birthday": "1979-04-19"
          },
          "document": "1cf7d351a96696450ba893f6720463599dd8c34e0aeda803d",
          "action": "Sign",
          "viewed": null,
          "signed": null,
          "rejected": null,
          "biometric_unapproved": null,
          "biometric_approved": null,
          "biometric_rejected": null,
          "events": [],
          "created_at": "2025-01-30T12:48:59.000000Z"
        }
      ],
      "stop_on_rejected": true,
      "qualified": false,
      "ignore_cpf": false,
      "sortable": false,
      "is_blocked": false,
      "sandbox": false,
      "api": false,
      "scrolling_required": false,
      "locale": {
        "country": "BR",
        "language": "pt-BR",
        "timezone": "America/Sao_Paulo",
        "date_format": "d/m/Y"
      },
      "email_template_id": null,
      "expiration_at": null,
      "notify_in": null,
      "reminder": null,
      "reply_to": null,
      "signatures_count": 2,
      "signed_count": 0,
      "rejected_count": 0,
      "files": {
        "original": "https://storage.googleapis.com/f77-dev/9be6ab49679f844230682d7335230029/3jTmKFWps4mIaMByzrj7zmFywm4a6LYIjUDfNhZ7.original.pdf",
        "signed": "https://painel.autentique.com.br/documentos/1cf7d351a96696450ba893f6720463599dd8c34e0aeda803d/assinado.pdf",
        "certified": "https://painel.autentique.com.br/documentos/1cf7d351a96696450ba893f6720463599dd8c34e0aeda803d/certificado.pdf",
        "pades": "https://painel.autentique.com.br/documentos/1cf7d351a96696450ba893f6720463599dd8c34e0aeda803d/pades.pdf"
      },
      "created_at": "2025-01-30T12:48:58.000000Z",
      "updated_at": "2025-01-30T12:49:56.000000Z",
      "deleted_at": null,
      "deadline_at": null,
      "lifecycle_in": "2030-01-30T03:00:00.000000Z"
    },
    "previous_attributes": {
      "signatures_count": 3
    },
    "created_at": "2025-01-30T12:49:56.342822Z"
  }
}
```

### Objeto de Assinatura

```json
{
  "id": "MjV8OTQ4OGNlMTEtNzBiZi00ZGI3LTg1OGItZDc2ZjFkZDI5MGRj",
  "object": "webhook",
  "name": "sig",
  "format": "json",
  "url": "https://essa-url-nao-existe.autentique.com.br/webhooks",
  "event": {
    "id": "9488ce11-70bf-4db7-858b-d76f1dd290dc",
    "object": "event",
    "organization": 1519203,
    "type": "signature.accepted",
    "data": {
      "public_id": "f8911dcd-dfcd-11ef-9465-42010a2b610e",
      "object": "signature",
      "user": {
        "name": "Felipe Autentique",
        "company": null,
        "email": "felipe@autentique.com",
        "phone": null,
        "cpf": "03351152094",
        "birthday": "2002-03-04"
      },
      "mail": {
        "sent": "2025-04-09 09:21:35",
        "opened": "2025-04-09 09:25:35",
        "refused": null,
        "delivered": null,
        "reason": null
      },
      "document": "f48a8b465d02dd87559e08f06c41e3b6d548c4d7ad835eb0f",
      "action": "Sign",
      "viewed": "2025-01-31T12:22:01.000000Z",
      "signed": "2025-01-31T12:22:30.000000Z",
      "rejected": null,
      "biometric_unapproved": null,
      "biometric_approved": null,
      "biometric_rejected": null,
      "events": [
        {
          "type": "viewed", // Pode ser: viewed, accepted, rejected, biometric_approved, biometric_rejected, biometric_unapproved
          "document": "f48a8b465d02dd87559e08f06c41e3b6d548c4d7ad835eb0f",
          "user": {
            "uuid": "ebcca9bc391a60336e777a23d32ada4410fe8b",
            "name": null,
            "email": "felipe@autentique.com",
            "cpf": null,
            "birthday": null
          },
          "geolocation": {
            "country": "Brazil",
            "countryISO": "BR",
            "state": "Rio Grande do Sul",
            "stateISO": "RS",
            "city": "Erechim",
            "zipcode": "99704094",
            "latitude": -27.6767,
            "longitude": -52.2559
          },
          "reason": null,
          "ip": "192.168.65.1",
          "port": 29317,
          "created_at": "2025-03-18T16:22:49.000000Z"
        },
        ...
      ]
      "created_at": "2025-01-31T12:22:00.000000Z"
    },
    "previous_attributes": [],
    "created_at": "2025-01-31T12:22:30.495056Z"
  }
}
```

### Objeto de Membro

```json
{
  "id": "MjZ8NzY1MjY1NTAtNzg3YS00YjU3LTk2MWYtN2EwMThlOTFkOGRj",
  "object": "webhook",
  "name": "memb",
  "format": "json",
  "url": "https://essa-url-nao-existe.autentique.com.br/webhooks",
  "event": {
    "id": "76526550-787a-4b57-961f-7a018e91d8dc",
    "object": "event",
    "organization": 1519203,
    "type": "member.created",
    "data": {
      "user": {
        "name": "Felipe Autentique",
        "company": null,
        "email": "felipe@autentique.com.br",
        "phone": null,
        "cpf": "03351152094",
        "birthday": "2002-03-04"
      },
      "group": {
        "uuid": "96d50d5a-b69c-4c84-b2d9-3f37c227cebf",
        "name": "Administrador",
        "organization": 1519203,
        "permissions": {
          "overwrite_permissions": true,
          "create_documents": true,
          "sign_documents": true,
          "delete_documents": true,
          "archive_documents": true,
          "view_documents_gr": true,
          "view_folders_gr": true,
          "actions_folders_gr": true,
          "actions_documents_gr": true,
          "actions_templates_gr": true,
          "actions_members_oz": true,
          "actions_groups_oz": true,
          "actions_webhooks_oz": true,
          "view_documents_oz": true,
          "view_member_documents_oz": true,
          "view_group_documents_oz": true,
          "view_folders_oz": true,
          "view_member_folders_oz": true,
          "view_group_folders_oz": true,
          "actions_documents_oz": true,
          "view_invoices_oz": true,
          "change_plan_oz": true,
          "actions_folders_oz": true,
          "change_appearances_oz": true,
          "change_whitelabel_oz": false,
          "enterprise_access": false
        },
        "configs": {
          "geral": false,
          "sobrescrever_modelos": false,
          "overwrite_template_group": false,
          "sobrescrever_organizacao": false
        }
      },
      "permissions": {
        "overwrite_permissions": false,
        "create_documents": true,
        "sign_documents": true,
        "delete_documents": true,
        "archive_documents": true,
        "view_documents_gr": true,
        "view_folders_gr": true,
        "actions_folders_gr": false,
        "actions_documents_gr": false,
        "actions_templates_gr": false,
        "actions_members_oz": false,
        "actions_groups_oz": false,
        "actions_webhooks_oz": false,
        "view_documents_oz": true,
        "view_member_documents_oz": false,
        "view_group_documents_oz": false,
        "view_folders_oz": true,
        "view_member_folders_oz": false,
        "view_group_folders_oz": false,
        "actions_documents_oz": false,
        "view_invoices_oz": true,
        "change_plan_oz": false,
        "actions_folders_oz": false,
        "change_appearances_oz": false,
        "change_whitelabel_oz": false,
        "enterprise_access": false
      }
    },
    "previous_attributes": [],
    "created_at": "2025-01-31T12:24:48.477885Z"
  }
}
```

## Tipos de eventos

A API do Autentique utiliza eventos específicos disparados quando certas ações ocorrem.

Todos os webhooks enviados estão relacionados a um evento de um determinado recurso, definido pelo campo “type”. O conteúdo dos webhooks varia apenas entre os diferentes tipos de eventos.

Esses eventos são classificados em três grandes categorias:

* **Document**: Eventos relacionados à criação, modificação e conclusão de\
  documentos.
* **Signature**: Eventos relacionados ao fluxo de assinaturas nos documentos.
* **Member**: Eventos relacionados aos membros pertencentes à organização.

### Eventos de Document

<table><thead><tr><th width="279">Evento</th><th>Descrição</th></tr></thead><tbody><tr><td>document.created</td><td>Disparado quando um novo documento é criado na aplicação.</td></tr><tr><td>document.updated</td><td>Disparado quando um documento existente é atualizado ou editado (exemplo: alteração nas configurações adicionais).</td></tr><tr><td>document.deleted</td><td>Disparado quando um documento é removido definitivamente da aplicação.</td></tr><tr><td>document.finished</td><td>Disparado quando todas as assinaturas ou etapas associadas ao documento são concluídas com sucesso.</td></tr></tbody></table>

### Eventos de Signature

<table><thead><tr><th width="276">Evento</th><th>Descrição</th></tr></thead><tbody><tr><td>signature.created</td><td>Disparado quando uma nova solicitação de assinatura é criada para um signatário em um documento.</td></tr><tr><td>signature.updated</td><td>Disparado quando ocorre uma alteração ou atualização na solicitação de assinatura. Como uma mudança nas condições ou na situação da assinatura (exemplo: posicionar uma assinatura invisível).</td></tr><tr><td>signature.deleted</td><td>Disparado quando um signatário pendente é removido do documento.</td></tr><tr><td>signature.viewed</td><td>Disparado quando o signatário visualiza o documento pela primeira vez, indicando que ele acessou o conteúdo, mas ainda não realizou nenhuma outra ação.</td></tr><tr><td>signature.accepted</td><td>Disparado quando o signatário conclui o processo de assinatura com sucesso.</td></tr><tr><td>signature.rejected</td><td>Disparado quando o signatário recusa explicitamente a assinatura.</td></tr><tr><td>signature.biometric_approved</td><td>Disparado quando a verificação biométrica do signatário é validada com sucesso pelo criador do documento ou uma validação automática.</td></tr><tr><td>signature.biometric_unapproved</td><td>Disparado quando a assinatura de um documento tem verificações pendentes. Como na validação manual que fica em um estado de aprovação que tem que ser feita pelo responsável pelo documento.</td></tr><tr><td>signature.biometric_rejected</td><td>Disparado quando a biometria é rejeitada pelo autor do documento.</td></tr><tr><td>signature.biometric_reset</td><td>Disparado quando uma aprovação manual é rejeitada com o parâmetro "Solicitar nova validação ao rejeitar" habilitado.</td></tr><tr><td>signature.delivery_failed</td><td>Disparado quando houve um erro no envio de email para o signatário.</td></tr></tbody></table>

### Eventos de Member

<table><thead><tr><th width="279"></th><th></th></tr></thead><tbody><tr><td>member.created</td><td>Disparado quando um novo membro é adicionado à organização.</td></tr><tr><td>member.deleted</td><td>Disparado quando um membro é removido da organização.</td></tr></tbody></table>

## *Webhooks* que falharam

Quando um *webhook* é enviado, mas não é recebido com sucesso pelo *endpoint* cadastrado, ele é adicionado à lista de *webhooks* não entregues no log de erros, disponível em nossa [página de cadastro de endpoints de webhooks](https://painel.autentique.com.br/perfil/webhooks).

Os *webhooks* nessa lista passarão por até três novas tentativas de envio, realizadas após 60, 120 e 300 segundos. Caso todas as tentativas falhem, o evento permanecerá na lista por até 14 dias.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.autentique.com.br/api/2/integracao/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
