WEBHOOKS



Overview



A ocorrência de certos eventos pode ser importante em diversos fluxos na integração de uma aplicação com a Stone Banking API. Por isso, utilizamos webhooks para notificar as aplicações integradas da ocorrência desses eventos.

Para sua utilização, é preciso que a aplicação tenha cadastrado no formulário a URI para a qual enviaremos um POST com os dados do evento. A aplicação deverá estar preparada para lidar com cada evento de forma adequada.

Ao enviar um webhook, esperamos receber uma resposta do range 200 que indica o sucesso no recebimento dessa notificação por parte da sua aplicação. Caso a resposta seja um código diferente do range 200, encaramos como falha no recebimento do webhook e repetimos o envio até receber um sucesso ou batermos o número máximo de 50 tentativas no envio de um evento. Essas tentativas são realizadas ao longo do dia, de forma assíncrona, sem um padrão de horários de tentativas.

A aplicação irá receber webhooks dos eventos que acontecerem na(s) conta(s) sobre as quais ela opera dado o escopo de funcionalidades disponíveis na sua aplicação e também recebem os eventos relacionados às contas sobre as quais foi dado consentimento.
Vale lembrar que os webhooks serão enviados independente se o evento for via API ou via app.


Virada em Produção

É imprescindível que a aplicação esteja recebendo e tratando de forma adequada todos os tipos de webhook referentes às funcionalidades que implementou.


Eventos que geram notificações



Emissão de Boleto:

Tipo de Evento Descrição
barcode_payment_invoice_created Representa a criação/emissão de um boleto.
barcode_payment_invoice_registered Representa o registro de um boleto.
barcode_payment_invoice_payment_promissed Representa que o pagamento do boleto foi acolhido em alguma instituição.
inbound_barcode_payment_validated Representa que o pagamento do boleto emitido foi aceito na instituição.
barcode_payment_invoice_settled Representa que o pagamento do boleto foi confirmado e liquidado.
barcode_payment_invoice_expired Representa que o boleto está expirado.
barcode_payment_invoice_cancelled Representa que o boleto está cancelado.
cash_in_barcode_payment Representa a entrada do valor do boleto na conta do beneficiário.
inbound_barcode_payment_refunded Representa que o pagamento do boleto foi devolvido.

Pagamento de Boletos, Contas e Tributos:

Tipo de Evento Descrição
cash_in_payment_refund Representa o reembolso de um pagamento malsucedido.
cash_out_payment Representa a criação de um pagamento.
cash_out_payment_failed Representa a falha na criação de um pagamento.
cash_out_payment_scheduled Representa o agendamento de um pagamento.
cash_out_payment_scheduled_failed Representa a falha no agendamento de um pagamento.
cash_out_payment_approved Representa a aprovação de um pagamento.
cash_out_payment_execution_started Representa o início da execução de um pagamento.
cash_out_payment_cancelled Representa o cancelamento de um pagamento.
cash_out_payment_rejected Representa a rejeição de um pagamento.
cash_out_payment_finished Representa a finalização de um pagamento.
cash_out_payment_expired Representa a expiração de um pagamento.

Transferência interna:

Tipo de Evento Descrição
cash_in_internal_transfer Representa o recebimento de uma transferência interna.
cash_out_internal_transfer Representa a criação de uma transferência interna.
cash_out_internal_transfer_failed Representa a falha na criação de uma transferência interna.
cash_out_internal_transfer_scheduled Representa o agendamento de uma transferência interna.
cash_out_internal_transfer_scheduled_failed Representa a falha no agendamento de uma transferência interna.
cash_out_internal_transfer_approved Representa a aprovação de uma transferência interna.
cash_out_internal_transfer_cancelled Representa o cancelamento de uma transferência interna.
cash_out_internal_transfer_rejected Representa a rejeição de uma transferência interna.
cash_out_internal_transfer_finished Representa a finalização de uma transferência interna.
cash_out_internal_transfer_expired Representa a expiração de uma transferência interna.

Transferência externa:

Tipo de Evento Descrição
cash_in_external_transfer Representa o recebimento de uma transferência externa.
cash_in_external_transfer_refund Representa o reembolso de uma transferência externa malsucedida.
cash_out_external_transfer Representa a criação de uma transferência externa.
cash_out_external_transfer_failed Representa a falha na criação de uma transferência externa.
cash_out_external_transfer_scheduled Representa o agendamento de uma transferência externa.
cash_out_external_transfer_scheduled_failed Representa a falha no agendamento de uma transferência externa.
cash_out_external_transfer_approved Representa a aprovação de uma transferência externa.
cash_out_external_transfer_execution_started Representa o início da execução de uma transferência externa.
cash_out_external_transfer_cancelled Representa o cancelamento de uma transferência externa.
cash_out_external_transfer_rejected Representa a rejeição de uma transferência externa.
cash_out_external_transfer_finished Representa a finalização de uma transferência externa.
cash_out_external_transfer_expired Representa a expiração de uma transferência externa.

Pix:

Tipo de Evento Descrição
pix_entries_key_created Representa a criação de uma chave Pix.
pix_entries_deleted Representa a deleção de uma chave Pix.
pix_entries_failed Representa a falha na criação de uma chave Pix.
pix_entries_claims_waiting_resolution Representa que estamos aguardando a resposta da cliente sobre a claim (confirmação ou cancelamento).
pix_entries_claims_confirmed Representa que a cliente confirmou a claim, passando a chave para outra instituição.
pix_entries_claims_automatically_confirmed_waiting_resolution Representa que a reivindicação de posse foi automaticamente confirmada porque a cliente não respondeu no prazo de 7 dias corridos.
pix_entries_claims_automatically_confirmed Representa que foi realizada a confirmação da reivindicação de posse automaticamente, porque a cliente não respondeu dentro dos 14 dias de prazo. Aqui a reivindicação não pode ser mais cancelada.
pix_entries_claims_cancelled Representa cancelamento da solicitação da claim por parte da cliente, mantendo a chave cadastrada na conta original.
pix_entries_claims_automatically_cancelled Representa cancelamento automático da solicitação de portabilidade, por falta de resposta da cliente nos 7 dias de prazo.
pix_payment_invoice Representa um QR Code dinâmico imediato.
pix_payment_invoice_with_due_date Representa um QR Code dinâmico com vencimento, multa, juros, desconto e abatimento.
pix_invoice_payment_created Representa a criação de um QR Code dinâmico.
pix_invoice_payment_cancelled Representa o cancelamento de um QR Code dinâmico.
pix_invoice_payment_paid Representa o pagamento com sucesso de um QR Code dinâmico.
inbound_pix_payment Representa um cash-in de Pix.
pix_inbound_payment_received Representa o recebimento de um cash-in de Pix, podendo estar associado a um QR Code estatático, dinâmico imediato ou dinâmico com vencimento. Essa associação será representada pela presença de um dos seguintes atributos dentro do target_data:
- pix_payment_invoice: Invoice dinâmica imediata;
- pix_payment_static_invoice: Invoice estática;
- pix_payment_invoice_with_due_date: Invoice dinâmica com vencimento, multa, juros, desconto e abatimento.
pix_refund_payment Representa uma devolução (cash-out) associada a um inbound payment (cash-in).
pix_inbound_payment_refund_created Representa a criação de uma devolução associada a uma transação Pix. Lembrando que estamos falando da devolução (cash-out) de um cash-in.
pix_inbound_payment_refund_failed Representa que houve falha na criação da devolução (cash-out) de um cash-in de Pix.
pix_inbound_payment_refund_money_reserved Representa a reserva de dinheiro (impacto no saldo da cliente) após a criação com sucesso de uma devolução (cash-out) de um cash-in de Pix.
pix_inbound_payment_refund_settled Representa que a devolução (cash-out) de um cash-in de Pix foi realizada com sucesso.
pix_inbound_payment_refund_reversed Representa que a devolução (cash-out) de um cash-in de Pix foi rejeitada.
pix_outbound_payment_created Representa a criação de um cash-out de Pix.
pix_outbound_payment_money_reserved Representa a reserva de dinheiro (impacto no saldo da cliente) após a criação com sucesso de um cash-out de Pix.
pix_outbound_payment_failed Representa a falha na criação de um cash-out de Pix.
pix_outbound_payment_settled Representa a liquidação com sucesso de um cash-out de Pix.
pix_outbound_payment_refunded Representa a devolução de um cash-out de Pix.

Consentimento:

Tipo de Evento Descrição
consent_request Representa a confirmação do pedido de consentimento por parte do user.

Abertura de conta:

Tipo de Evento Descrição
sign_up_created Representa o pedido de abertura de conta.
sign_up_status_updated Representa quando há uma atualização dos dados na abertura da conta.
sign_up_resource_details_updated Representa o envio de diversos webhooks de acordo com a evolução da abertura de conta. Exemplo: account_created, user_email_verified, ….


Tipo de Evento Descrição
order_created Representa a criação do link de pagamento.
Também é enviado um segundo webhook desse tipo que representa que o pagador executou o pagamento.
Nesse caso, o campo status do webhook terá seu valor alterado para pending.
order_paid Representa o pagamento em si do link de pagamento.
order_closed Representa a finalização do link de pagamento.

Estrutura do header das notificações


x-stone-webhook-event-id: "930bbd6d-0c7a-4fe4-8b50-4b82a20cb847"
x-stone-webhook-event-type: "cash_out_internal_transfer"

A idempotência de webhooks deverá ser validada no campo ‘x-stone-webhook-event-id’ disponível no header.


Estrutura do payload das notificações



Campo Descrição
env Especifica de qual ambiente partiu o evento. Valores possíveis: sandbox ou production.
event_type Especifica qual tipo de evento disparou a notificação. Veja os valores possíveis aqui.
id É o identificador da notificação.
event_notified_at É a hora em que a notificação está sendo enviada.
event_happened_at É a hora em que o evento ocorreu.
target_data Objeto com as informações detalhadas do recurso que gerou o evento. Apesar de ter campos variáveis, sempre conterá o campo account_id.
target_detail_uri É o endereço onde se pode consultar os detalhes do recurso.
target_id É o identificador do recurso que gerou o evento.
target_statement_uri É o endereço da entrada no extrato referente ao recurso.
target_type É o tipo do recurso.

Inclusão de campos

A implementação não deve ser strict no parser do payload. Ao longo do tempo, os payloads podem sofrer a inclusão de campos.


Exemplo:

{
   "env":"sandbox",
   "event_type":"cash_in_internal_transfer",
   "event_happened_at":"2020-05-13T14:58:15Z",
   "event_notified_at":"2020-05-13T14:58:15Z",
   "iat":1589381895,
   "id":null,
   "jti":"2o79sqemde14mv76eo00jsc3",
   "nbf":1589381895,
   "target_data":{
      "account_id":"09c016b2-876a-450a-9f40-316f8e2f8778",
      "amount":1,
      "balance_after":null,
      "balance_before":null,
      "counter_party":{
         "account":{
            "account_code":"841412",
            "branch_code":"1",
            "institution":"16501555",
            "institution_name":"Stone Pagamentos S.A."
         },
         "entity":{
            "name":"Loja Da Maria"
         }
      },
      "created_at":"2020-05-13T14:58:15Z",
      "description":"",
      "id":"54abd61c-3b18-401c-9816-951cbe135149",
      "operation":"credit",
      "status":"FINISHED",
      "type":"internal"
   },
   "target_detail_uri":null,
   "target_id":null,
   "target_statement_uri":"https://sandbox-api.openbank.stone.com.br/api/v1/statement/entries/54abd61c-3b18-401c-9816-951cbe135149",
   "target_type":"internal_transfer"
}

Trabalhando com Webhooks



Webhooks Seguros


Nossa API envia webhooks de forma segura para evitar que eles sejam abertos e/ou alterados. Isso é feito em duas etapas:

  • Assinatura do conteúdo usando uma das nossas chaves públicas.
  • Ciframento do resultado usando a chave pública do destinatário.

Como eu posso abrir de forma segura o conteúdo de um webhook?

Para poder visualizar o conteúdo de um webhook, é necessário fazer o caminho inverso:

  1. Decifrar o conteúdo do webhook usando sua chave privada. O resultado é um token JWT assinado com a nossa chave;
  2. Verificar a assinatura do token com a nossa chave pública.

1 - Decifrar o conteúdo do webhook


O webhook irá chegar com um payload parecido com este:

{
  "encrypted_body": "xxxxxxxxxxxxxx conteúdo cifrado"
}

O valor do campo “encrypted_body” é um token no formato JWE (JSON Web Encryption) compacto. Esse formato é um padrão e várias linguagens possuem bibliotecas prontas para ele.

O algoritmo utilizado é: RSA-OAEP-256 com A256GCM. Para decifrar o conteúdo, é necessária uma biblioteca que suporte esses algoritmos como:

A chave a ser utilizada tem que ser a chave privada par da chave pública usada na autenticação da aplicação.

Um exemplo para decodificar o webhook pode ser verificado no código em Python abaixo:

from jwcrypto import jwk, jwe
from jwcrypto.common import json_encode
import jwt
import time
import subprocess
import simplejson as json

webhook_response = {"encrypted_body":"eyJhbGciOiJSU0EtT0FFz2C15nwK...kadnFeyS100SDgTPA9pO5GS4_jZw.SwKpGLZ_f2-q-Gw"}

token = webhook_response['encrypted_body']

with open("private-key.pem", "rb") as pemfile:
    jwk_private_key = jwk.JWK.from_pem(pemfile.read())

jwetoken = jwe.JWE()
jwetoken.deserialize(token)
jwetoken.decrypt(jwk_private_key)
payload = jwetoken.payload.decode('utf-8')

jwt_decoded = jwt.decode(payload, options={"verify_signature": False})
jwt_parsed = json_encode(jwt_decoded)

subprocess.run('pbcopy', universal_newlines=True, input=jwt_parsed)

2 - Verificar a assinatura do token


Até este momento, deciframos apenas um conteúdo que pode ser gerado por qualquer um que tenha acesso à chave pública da aplicação.

Nós utilizamos a assinatura do conteúdo em token JWS (JSON Web Signing) compacto como uma outra camada de verificação de segurança no conteúdo do webhook.

Esse token possui em seus “claims” (alegações) o conteúdo do webhook. Para garantir que ele foi gerado pela Stone, você deve validar a assinatura dele consultando nossas chaves públicas de assinatura.

As chaves públicas da Stone estão publicadas aqui.

Repare que cada chave possui um kid (key ID) e um use (sig ou enc). O token JWS possui um header que diz qual é o ID da chave usada para assiná-lo. Assim, é necessário, em primeiro lugar, garantir que o token foi assinado com uma chave que está publicada pela Stone.

Para não ter que buscar a nossa chave pública a cada validação de assinatura, você pode salvar nossa chave do seu lado (por exemplo, através de um cache), de forma que, caso aconteça algum erro na validação, você volte a consultar o nosso serviço para buscar a chave pública mais atual. Assim você desonera sua aplicação sem perder a resiliência a mudanças de chave do nosso lado.

Veja aqui as melhores práticas para fazer essa implementação.

O algoritmo que usamos será sempre RS256. Há uma lista de bibliotecas que tratam de assinatura de token em jwt.io.


Segurança

É importante seguir os dois passos aqui: tanto decifrar quanto verificar a assinatura. Isso irá garantir que não apenas geramos algo que não pode ser lido por alguém que não possui a chave pública quanto que nosso conteúdo foi assinado pela Stone.

Também aconselhamos cuidado na configuração do cliente que irá consultar as nossas chaves. Certifique-se de que ele não está vulnerável a servidores intermediários propositalmente mal configurados. Um exemplo comum é um servidor configurado para protocolos conhecidamente falhos.

Sempre que estiver em dúvida, consulte nossa API com os IDs dos dados no conteúdo do webhook.

Mais informações sobre JavaScript Object Signing and Encryption, como JWTs, JWEs, JWSs, aqui.


Webhooks idempotentes


No geral um evento, quando recepcionado com sucesso, é enviado apenas uma vez pela Stone, mas é possível que em algum cenário extraordinário um webhook acabe sendo enviado mais de uma vez e por isso desenvolvemos uma lógica de idempotência de eventos.

Todo webhook gerado pela Stone leva consigo uma chave de idempotência enviada no campo x-stone-webhook-event-id do header.

É importante que essa chave seja sempre salva pela sua aplicação e que a cada novo evento você verifique a idempotência desse evento, evitando processar duas vezes um mesmo evento.


Endpoint para verificar webhooks enviados



Caso tenha dificuldade para identificar algum webhook enviado pela Stone na URI indicada para receber webhooks, disponibilizamos um endpoint que traz todos os webhooks já enviados para a sua aplicação.

Para acessar, basta fazer o comando abaixo, lembrando que no header deve ser enviado o Bearer token no campo authorization e que o client_id é o id da sua aplicação, passado pelo time de integração.

GET https://sandbox-api.openbank.stone.com.br/api/v1/applications/application:{{client_id}}/webhooks

Exemplo de Response

{
  "client_id": "application:de4d224b-3f79-4b4d-8ff5-c1bc475830e5",
  "created_at": "2021-06-02T19:40:24Z",
  "data": {
    "env": "sandbox",
    "event_happened_at": "2021-06-02T19:40:23Z",
    "event_notified_at": "2021-06-02T19:40:24Z",
    "event_type": "cash_out_internal_transfer_finished",
    "id": "7919b78a-630e-4ad4-bb12-91eec729175d",
    "target_data": {
      "account_id": "6745bdf0-7647-40cd-8666-d78bb15fbd38",
      "amount": 4000,
      "approval_expired_at": null,
      "approved_at": "2021-06-02T19:40:23Z",
      "approved_by": "user:24017c13-1f9d-44e4-b4af-007f695cece3",
      "batch_id": null,
      "cancelled_at": null,
      "cancelled_by": null,
      "created_at": "2021-06-02T19:40:23Z",
      "created_by": "user:24017c13-1f9d-44e4-b4af-007f695cece3",
      "description": "",
      "failed_at": null,
      "failure_reason_code": null,
      "failure_reason_description": null,
      "fee": 0,
      "finished_at": "2021-06-02T19:40:23Z",
      "id": "7919b78a-630e-4ad4-bb12-91eec729175d",
      "rejected_at": null,
      "rejected_by": null,
      "scheduled_to": null,
      "settled_at": "2021-06-02T19:40:23Z",
      "status": "FINISHED",
      "target": {
        "account": {
          "account_code": "55382931"
        },
        "entity": {
          "name": ""
        }
      },
      "target_account_code": "55382931",
      "target_account_owner_name": ""
    },
    "target_detail_uri": "https://sandbox-api.openbank.stone.com.br/api/v1/internal_transfers/7919b78a-630e-4ad4-bb12-91eec729175d",
    "target_id": "7919b78a-630e-4ad4-bb12-91eec729175d",
    "target_statement_uri": null,
    "target_type": "internal_transfer_finished"
  }
}

Última modificação: 26.08.2021