GuiasReferência de API

Índice

Webhook é o meio que informamos nossos clientes quando alguma mudança ocorre em algum recurso.

Ao receber um webhook o cliente deve responder com HTTP status 200.
Como esses eventos contém informacões valiosas sobre a situacão de um recurso (transacão, KYC, etc.), caso seja devolvido qualquer outro status de resposta novas tentativas de envio dessa mensagem serão realizadas, até uma resposta com status 200 ser recebida, a fim de garantir a entrega.

Para ter acesso a esse serviço entre em contato com nossa equipe para configurar as informacões necessárias. Assim poderemos entender quais tipos de evento seu negócio tem interesse em ser informado, a URL que será usada para enviarmos as mensagens e as chaves para efetuar a criptografia dos webhooks (ver mais em Segurança).

Abaixo é explicado um pouco mais sobre todos os tipos de eventos que notificamos, com exemplos, e a seguir existe uma seção de segurança dos webhooks.

Formato dos Webhooks

Formato genérico do body de um webhook:

{
  "id": "string",
  "event_type": "string",
  "resource_type": "string",
  "resource_id": "integer",
  "resource": "object",
  "webhook_url": "string"
}

Dividimos nossos webhooks por entidades, as entidades que temos webhooks disponíveis são:

Webhook de Transação

Os eventos de um webhook de transação são os seguintes:

event_typedescrição
transaction_createdQuando uma transação é criada
transaction_approvedQuando o status de uma transação muda para aprovado
transaction_deniedQuando o status de uma transação muda para negado

Abaixo consta exemplos de como é o corpo de cada evento transacional descriptografado. Vale notar que todas as transações, independente de sua categoria, são notificadas por esses eventos (transaction_created, transaction_approved e transaction_denied), a única variação em webhooks de categorias diferentes é o formato do resource.metadata. É possível ver uma lista dos possíveis metadatas na seção de Transações da aba Conceitos.

transaction_created
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "event_type": "transaction_created",
  "resource_type": "transaction",
  "resource_id": 1234,
  "resource": {
    "id": 1234,
    "uuid": "ff5520e7-4aa1-4790-b932-9d45e64acc3f",
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "pending",
    "category": "BILL_PAYMENT",
    "payment_method": "CONTA",
    "description": "Conta de luz",
    "custom_id": "meu-id-customizado",
    "operation_id": "998f0dac-bd04-4a33-8970-8acc6469753e",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "metadata": {
      "fine_value": 0,
      "interest_value": 0,
      "discount_value": 0,
      "original_value": 5000,
      "due_date": "2021-05-05",
      "scheduled_to": "2021-05-04",
      "writable_line": "23793381286004720838816000063301284580000002000",
      "bill_type": "Concessionária"
    }
  }
}
transaction_approved
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "event_type": "transaction_approved",
  "resource_type": "transaction",
  "resource_id": 1234,
  "resource": {
    "id": 1234,
    "uuid": "ff5520e7-4aa1-4790-b932-9d45e64acc3f",
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "approved",
    "category": "BILL_PAYMENT",
    "payment_method": "CONTA",
    "description": "Conta de luz",
    "custom_id": "meu-id-customizado",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:08:01+00:00",
    "metadata": {
      "due_date": "2021-05-05",
      "scheduled_to": "2021-05-04",
      "writable_line": "23793381286004720838816000063301284580000002000",
      "bill_type": "Concessionária"
    }
  }
}
transaction_denied
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "event_type": "transaction_denied",
  "resource_type": "transaction",
  "resource_id": 1234,
  "resource": {
    "id": 1234,
    "uuid": "ff5520e7-4aa1-4790-b932-9d45e64acc3f",
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "denied",
    "category": "BILL_PAYMENT",
    "payment_method": "CONTA",
    "description": "Conta de luz",
    "custom_id": "meu-id-customizado",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:08:01+00:00",
    "metadata": {
      "due_date": "2021-05-05",
      "scheduled_to": "2021-05-04",
      "writable_line": "23793381286004720838816000063301284580000002000",
      "bill_type": "Concessionária"
    }
  }
}

Para ver os diferentes tipos de transações e seus metadatas, verificar a aba de conceitos.

Webhook de KYC

Os eventos de um webhook de KYC são os seguintes:

event_typedescrição
kyc_createdQuando um KYC é criada
kyc_approvedQuando o status de um KYC muda para aprovado
kyc_deniedQuando o status de um KYC muda para negado

Formato do body de webhooks de KYC:

kyc_created
{
  "id": "f7911c86-9c95-47ea-b7dc-c95de57d21ef",
  "event_type": "kyc_created",
  "resource_type": "kyc",
  "resource_id": 1232,
  "resource": {
    "id": 1232,
    "status": "pending",
    "status_reason": "waiting_analysis",
    "account_id": 1233,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-01T04:05:06+00:00",
    "operator": {
      "name": "Fulano de Tal",
      "cpf": "12312312300",
      "date_of_birth": "2001-05-05",
      "phone_number": "21912345678",
      "attached_document_type": "rg"
    },
    "owner": {
      "name": "Fulano de Tal",
      "document": "12312312300",
      "document_type": "cpf",
      "date_of_birth": "2001-05-05"
    },
    "address": {
      "postal_code": "12312000",
      "country": "BR",
      "state": "RJ",
      "city": "Rio de Janeiro",
      "neighborhood": "Meier",
      "street": "Piedade",
      "street_number": "1234",
      "door": "Fundos"
    }
  }
}
kyc_approved
{
  "id": "f7911c86-9c95-47ea-b7dc-c95de57d21ef",
  "event_type": "kyc_approved",
  "resource_type": "kyc",
  "resource_id": 1232,
  "resource": {
    "id": 1232,
    "status": "approved",
    "status_reason": "ok",
    "account_id": 1233,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-01T04:05:06+00:00",
    "operator": {
      "name": "Fulano de Tal",
      "cpf": "12312312300",
      "date_of_birth": "2001-05-05",
      "phone_number": "21912345678",
      "attached_document_type": "rg"
    },
    "owner": {
      "name": "Fulano de Tal",
      "document": "12312312300",
      "document_type": "cpf",
      "date_of_birth": "2001-05-05"
    },
    "address": {
      "postal_code": "12312000",
      "country": "BR",
      "state": "RJ",
      "city": "Rio de Janeiro",
      "neighborhood": "Meier",
      "street": "Piedade",
      "street_number": "1234",
      "door": "Fundos"
    }
  }
}
kyc_denied
{
  "id": "f7911c86-9c95-47ea-b7dc-c95de57d21ef",
  "event_type": "kyc_denied",
  "resource_type": "kyc",
  "resource_id": 1232,
  "resource": {
    "id": 1232,
    "status": "denied",
    "status_reason": "invalid_photo",
    "account_id": 1233,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-01T04:05:06+00:00",
    "operator": {
      "name": "Fulano de Tal",
      "cpf": "12312312300",
      "date_of_birth": "2001-05-05",
      "phone_number": "21912345678",
      "attached_document_type": "rg"
    },
    "owner": {
      "name": "Fulano de Tal",
      "document": "12312312300",
      "document_type": "cpf",
      "date_of_birth": "2001-05-05"
    },
    "address": {
      "postal_code": "12312000",
      "country": "BR",
      "state": "RJ",
      "city": "Rio de Janeiro",
      "neighborhood": "Meier",
      "street": "Piedade",
      "street_number": "1234",
      "door": "Fundos"
    }
  }
}

Webhook de Conta de Pagamento

Os eventos de um webhook de conta de pagamento são os seguintes:

event_typedescrição
account_createdQuando uma conta de pagamento é criada
account_approvedQuando o status de uma conta de pagamento passa para aprovada
account_deniedQuando o status de uma conta de pagamento passa para rejeitada

Formato do body de webhooks de conta de pagamento:

account_created
{
"id":"1760a70b-198d-4c18-bb66-8094809a53fd",
"client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
"event_type":"account_created",
"resource_type":"account",
"resource_id":304,
"resource":{
  "id": "56d63984-cdc6-46a0-9c91-fc6addcbb0b9",
  "user_id": 1234,
  "account_code":"134312",
  "branch_code":"1",
  "account_type":"PG",
  "custom_id": "6b6d00de-825b-4142-8ab7-43306ea2b781",
  "institution":{
    "code":"197",
    "ispb":"16501555",
    "name":"Stone Pagamentos S.A."
  },
  "status":"approved"
},
"webhook_url": "https://minhaurl.com/webhooks" 
}
account_approved
{
"id":"1760a70b-198d-4c18-bb66-8094809a53fd",
"client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
"event_type":"account_approved",
"resource_type":"account",
"resource_id":304,
"resource":{
  "id": "56d63984-cdc6-46a0-9c91-fc6addcbb0b9",
  "user_id": 1234,
  "account_code":"134312",
  "branch_code":"1",
  "account_type":"PG",
  "custom_id": "6b6d00de-825b-4142-8ab7-43306ea2b781",
  "institution":{
    "code":"197",
    "ispb":"16501555",
    "name":"Stone Pagamentos S.A."
  },
  "status":"approved"
},
"webhook_url": "https://minhaurl.com/webhooks" 
}
account_denied
{
"id":"1760a70b-198d-4c18-bb66-8094809a53fd",
"client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
"event_type":"account_denied",
"resource_type":"account",
"resource_id":304,
"resource":{
  "id": "56d63984-cdc6-46a0-9c91-fc6addcbb0b9",
  "user_id": 1234,
  "account_code":"134312",
  "branch_code":"1",
  "account_type":"PG",
  "custom_id": "6b6d00de-825b-4142-8ab7-43306ea2b781",
  "institution":{
    "code":"197",
    "ispb":"16501555",
    "name":"Stone Pagamentos S.A."
  },
  "status":"denied"
},
"webhook_url": "https://minhaurl.com/webhooks" 
}

Webhook de Chave Pix

Os eventos de um webhook de uma chave pix são:

event_typedescrição
pix_key_createdQuando uma chave pix é criada
pix_key_approvedQuando a chave pix é aprovada
pix_key_deniedQuando a chave pix é negada

Formato do body de um webhook de chave pix:

pix_key_created
{
  "id": "41b39a8b-333e-468c-92f9-58fd3abbec58",
  "event_type": "pix_key_created",
  "resource_type": "pix_key",
  "resource_id": 1337,
  "resource": {
    "id": 1337,
    "account_id": 567,
    "key": "23375e89-8dfd-457c-b856-4881460692f4",
    "key_type": "EVP",
    "status": "pending",
    "status_reason": "in_analysis",
    "custom_id": "meu-id-customizado",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "claims": [

    ]
  }
}
pix_key_approved
{
  "id": "41b39a8b-333e-468c-92f9-58fd3abbec58",
  "event_type": "pix_key_approved",
  "resource_type": "pix_key",
  "resource_id": 1337,
  "resource": {
    "id": 1337,
    "account_id": 567,
    "key": "23375e89-8dfd-457c-b856-4881460692f4",
    "key_type": "EVP",
    "status": "approved",
    "status_reason": "in_analysis",
    "custom_id": "meu-id-customizado",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "claims": [

    ]
  }
}
pix_key_denied
{
  "id": "41b39a8b-333e-468c-92f9-58fd3abbec58",
  "event_type": "pix_key_denied",
  "resource_type": "pix_key",
  "resource_id": 1337,
  "resource": {
    "id": 1337,
    "account_id": 567,
    "key": "23375e89-8dfd-457c-b856-4881460692f4",
    "key_type": "EVP",
    "status": "denied",
    "status_reason": "in_analysis",
    "custom_id": "meu-id-customizado",
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "claims": [

    ]
  }
}

Webhook de Agendamento

Os eventos de um webhook de um agendamento são:

event_typedescrição
scheduled_transaction_createdQuando um agendamento é criado
scheduled_transaction_approvedQuando o agendamento é aprovado
scheduled_transaction_deniedQuando o agendamento é negado

Formato do body de um webhook de agendamento:

scheduled_transaction_created
{
  "id": "5329b557-4afb-4286-91a3-9a50325ca863",
  "event_type": "scheduled_transaction_created",
  "resource_type": "scheduled_transaction",
  "resource_id": 513695,
  "resource": {
    "id": 513695,
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "pending",
    "category": "EXTERNAL_TRANFER",
    "payment_method": "TED",
    "description": "Transf do churrasco",
    "custom_id": "meu-id-customizado",
    "result_transaction_id": null,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "metadata": {
      "bank_code": "206",
      "agency_number": "001",
      "number": "12345",
      "type": "CC",
      "holder": {
        "name": "Teste TED",
        "document": "12312312345",
        "document_type": "cpf"
      }
    }
  }
}
scheduled_transaction_approved
{
  "id": "5329b557-4afb-4286-91a3-9a50325ca863",
  "event_type": "scheduled_transaction_approved",
  "resource_type": "scheduled_transaction",
  "resource_id": 513695,
  "resource": {
    "id": 513695,
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "approved",
    "category": "EXTERNAL_TRANFER",
    "payment_method": "TED",
    "description": "Transf do churrasco",
    "custom_id": "meu-id-customizado",
    "result_transaction_id": 45688,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "metadata": {
      "bank_code": "206",
      "agency_number": "001",
      "number": "12345",
      "type": "CC",
      "holder": {
        "name": "Teste TED",
        "document": "12312312345",
        "document_type": "cpf"
      }
    }
  }
}
scheduled_transaction_denied
{
  "id": "5329b557-4afb-4286-91a3-9a50325ca863",
  "event_type": "scheduled_transaction_denied",
  "resource_type": "scheduled_transaction",
  "resource_id": 513695,
  "resource": {
    "id": 513695,
    "account_id": 567,
    "wallet_slug": "DEFAULT",
    "value": 5000,
    "side": "debit",
    "status": "denied",
    "category": "EXTERNAL_TRANFER",
    "payment_method": "TED",
    "description": "Transf do churrasco",
    "custom_id": "meu-id-customizado",
    "result_transaction_id": null,
    "created_at": "2021-05-01T04:05:06+00:00",
    "updated_at": "2021-05-02T04:05:06+00:00",
    "metadata": {
      "bank_code": "206",
      "agency_number": "001",
      "number": "12345",
      "type": "CC",
      "holder": {
        "name": "Teste TED",
        "document": "12312312345",
        "document_type": "cpf"
      }
    }
  }
}

Webhook de Cartão

Os possíveis eventos de webhooks do ciclo de vida de um Cartão são os seguintes:

event_typedescrição
card_createdQuando um cartão é criada
card_waiting_first_activationQuando o cartão foi emitido e está aguardando o usuário receber para ativar
card_activeQuando o cartão é ativado
card_blockedQuando o cartão é bloqueado
card_canceledQuando o cartão é cancelado

De forma geral, o contrato desses tipos de evento são iguais, a principal informacão que muda é o conteúdo do campo status.

Abaixo é possível ver exemplos do corpo desses eventos:

card_created
{
  "id": "0a9f9a6d-1742-4b26-8ea5-b22f670b8de0",
  "event_type": "card_created",
  "resource_type": "card",
  "resource_id": 1409,
  "resource": {
    "id": 1409,
    "account_id": 3548,
    "embossing_name": "Exemplo Webhook",
    "shipping_address": {
      "country": "BR",
      "state": "Rio de Janeiro",
      "city": "Rio de Janeiro",
      "neighborhood": "Botafogo",
      "street": "Rua Voluntários da Pátria",
      "street_number": "1",
      "postal_code": "22270005",
      "door": "",
      "reference_point": "Teste de referência"
    },
    "masked_number": null,
    "card_type": "physical",
    "brand": "elo",
    "status": "pending",
    "expiration_date": null,
    "created_at": "2023-09-28T12:06:13.095-03:00",
    "updated_at": "2023-09-28T12:06:13.095-03:00",
    "webhook_url": null,
    "card_configuration_name": "ton_multi_visa_physical"
  }
}
card_waiting_first_activation
{
  "id": "0a9f9a6d-1742-4b26-8ea5-b22f670b8de0",
  "event_type": "card_waiting_first_activation",
  "resource_type": "card",
  "resource_id": 1409,
  "resource": {
    "id": 1409,
    "account_id": 3548,
    "embossing_name": "Exemplo Webhook",
    "shipping_address": {
      "country": "BR",
      "state": "Rio de Janeiro",
      "city": "Rio de Janeiro",
      "neighborhood": "Botafogo",
      "street": "Rua Voluntários da Pátria",
      "street_number": "1",
      "postal_code": "22270005",
      "door": "",
      "reference_point": "Teste de referência"
    },
    "masked_number": null,
    "card_type": "physical",
    "brand": "visa",
    "status": "waiting_first_activation",
    "expiration_date": null,
    "created_at": "2023-09-28T12:06:13.095-03:00",
    "updated_at": "2023-09-28T12:06:13.095-03:00",
    "webhook_url": null,
    "card_configuration_name": "ton_multi_visa_physical"
  }
}
card_active
{
  "id": "0a9f9a6d-1742-4b26-8ea5-b22f670b8de0",
  "event_type": "card_active",
  "resource_type": "card",
  "resource_id": 1409,
  "resource": {
    "id": 1409,
    "account_id": 3548,
    "embossing_name": "Exemplo Webhook",
    "shipping_address": {
      "country": "BR",
      "state": "Rio de Janeiro",
      "city": "Rio de Janeiro",
      "neighborhood": "Botafogo",
      "street": "Rua Voluntários da Pátria",
      "street_number": "1",
      "postal_code": "22270005",
      "door": "",
      "reference_point": "Teste de referência"
    },
    "masked_number": "************1672",
    "card_type": "physical",
    "brand": "visa",
    "status": "active",
    "expiration_date": "09/28",
    "created_at": "2023-09-28T12:06:13.095-03:00",
    "updated_at": "2023-09-28T12:06:13.095-03:00",
    "webhook_url": null,
    "card_configuration_name": "ton_multi_visa_physical"
  }
}
card_blocked
{
  "id": "0a9f9a6d-1742-4b26-8ea5-b22f670b8de0",
  "event_type": "card_blocked",
  "resource_type": "card",
  "resource_id": 1409,
  "resource": {
    "id": 1409,
    "account_id": 3548,
    "embossing_name": "Exemplo Webhook",
    "shipping_address": {
      "country": "BR",
      "state": "Rio de Janeiro",
      "city": "Rio de Janeiro",
      "neighborhood": "Botafogo",
      "street": "Rua Voluntários da Pátria",
      "street_number": "1",
      "postal_code": "22270005",
      "door": "",
      "reference_point": "Teste de referência"
    },
    "masked_number": null,
    "card_type": "physical",
    "brand": "visa",
    "status": "blocked",
    "expiration_date": null,
    "created_at": "2023-09-28T12:06:13.095-03:00",
    "updated_at": "2023-09-28T12:06:13.095-03:00",
    "webhook_url": null,
    "card_configuration_name": "ton_multi_visa_physical"
  }
}
card_canceled
{
  "id": "0a9f9a6d-1742-4b26-8ea5-b22f670b8de0",
  "event_type": "card_canceled",
  "resource_type": "card",
  "resource_id": 1409,
  "resource": {
    "id": 1409,
    "account_id": 3548,
    "embossing_name": "Exemplo Webhook",
    "shipping_address": {
      "country": "BR",
      "state": "Rio de Janeiro",
      "city": "Rio de Janeiro",
      "neighborhood": "Botafogo",
      "street": "Rua Voluntários da Pátria",
      "street_number": "1",
      "postal_code": "22270005",
      "door": "",
      "reference_point": "Teste de referência"
    },
    "masked_number": null,
    "card_type": "physical",
    "brand": "visa",
    "status": "canceled",
    "expiration_date": null,
    "created_at": "2023-09-28T12:06:13.095-03:00",
    "updated_at": "2023-09-28T12:06:13.095-03:00",
    "webhook_url": null,
    "card_configuration_name": "ton_multi_visa_physical"
  }
}

Webhook de rastreamento do cartão

Os possíveis eventos de webhooks de rastreamento de um cartão são os seguintes:

event_typedescrição
card_delivery_status_in_productionPeríodo entre a aprovação do cartão e a saída do cartão na transportadora
card_delivery_status_in_transitQuando um cartão está em rota de entrega pela transportadora
card_delivery_status_deliveredQuando o cartão é entregue na casa do cliente
card_delivery_status_resendQuando está repetindo a tentativa de entrega
card_delivery_status_other_problemQuando houve um problema na entrega do cartão
card_delivery_status_delivery_canceledQuando o cartão foi cancelado antes de ser entregue (irreversível)

De forma geral, o contrato desses tipos de evento são iguais, a principal informacão que muda é o conteúdo do campo delivery_status.

Abaixo é possível ver exemplos do corpo desses eventos:

card_delivery_status_in_production
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_in_production",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"in_production",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}
card_delivery_status_in_transit
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_in_transit",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"in_transit",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}
card_delivery_status_delivered
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_delivered",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"delivered",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}
card_delivery_status_resend
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_resend",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"resend",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}
card_delivery_status_other_problem
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_other_problem",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"other_problem",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}
card_delivery_status_delivery_canceled
{
  "id":"8cacfe65-72c6-4573-82ed-f27b5760e7d3",
  "client_id":"3bf90f00-b2f2-44da-8fc0-2476fab54c1c",
  "event_type":"card_delivery_status_delivery_canceled",
  "resource_type":"card",
  "resource_id":1468,
  "resource":{
    "id":1468,
    "account_id":3548,
    "embossing_name":"Reteste Webhook Status",
    "shipping_address":{
      "country":"BR",
      "state":"Rio de Janeiro",
      "city":"Rio de Janeiro",
      "neighborhood":"Botafogo",
      "street":"Rua Voluntários da Pátria",
      "street_number":"415",
      "postal_code":"22270005",
      "door":"",
      "reference_point":"Teste de referência"
    },
    "masked_number":"************8409",
    "card_type":"physical",
    "brand":"visa",
    "status":"waiting_first_activation",
    "expiration_date":"11/28",
    "created_at":"2023-11-16T10:48:33.906-03:00",
    "updated_at":"2023-11-16T14:13:23.387-03:00",
    "webhook_url":null,
    "card_configuration_name":"ton_multi_visa_physical",
    "delivery_status":"delivery_canceled",
    "correios_delivery_code":null,
    "issuer_tracking_id":"PI00000000090"
  },
  "webhook_url":null
}

Segurança

Os endpoints para webhooks devem ser públicos e podem conter cabeçalhos fixos com chaves de autenticação, caso precise, por exemplo: (Authorization: Basic ***).

Os webhooks são enviados criptografados e assinados, dessa forma apenas o correto destinatário do webhook poderá ler a mensagem e poderá garantir o remetente pela assinatura.

Formato de um JWT

Antes de comecar é importante entender um pouco sobre os JWTs e sua estrutura, pois a seguranca dos webhooks é baseada em boa parte no seu funcionamento.

Os JSON Web Tokens são constituídos por 3 partes separadas por pontos (.):

  • Headers (Cabecalhos)
  • Payload (Corpo)
  • Signature (Assinatura)

Portanto, o formato de um JWT costuma ser: hhhhh.ppppp.sssss

Para montar o JWT, o conteúdo das 3 partes são codificados em Base64. Gerando um token com a seguinte cara:
JWT

Por padrão, o corpo de um JWT não é criptografado (apenas transformado para Base64), portanto não é seguro trafegar informações sensíveis nele. Para isso é recomendado o uso de JWE (JSON Web Ecryption), onde é mantido a estrutura apresentada, porém o conteúdo do corpo é criptografado usando algum algoritmo de sua escolha.

Entendido esses conceitos básicos, é possível seguir para as próximas etapas, onde iremos configurar algumas coisas para criptografia e assinatura dos eventos de webhook.

Para mais informações sobre JSON Web Tokens acesse jwt.io.

Criptografia

Para efetuarmos a criptografia, precisamos que você gere um par de chaves RSA_2048 e nos envie a chave pública que será usada para gerar o token JWE.

As mensagens serão criptografadas com chave pública do cliente RSA 2048 bits usando o algoritmo A128CBC-HS256.

Segue um exemplo de como gerar esse par de chaves pelo terminal, usando o openssl:

$ openssl genrsa -out mykey.pem 2048
$ openssl rsa -in mykey.pem -pubout > mykey.pub

Esses comandos geram 2 chaves, a chave pública (mykey.pub) deve ser enviada à nós para efetuarmos a criptografia dos eventos.

Assinatura

O conteúdo no corpo do JWE, será um JWS (JSON Web Signature), um token assinado. A assinatura é feita com uma chave privada nossa RSA 2048 bits usando algoritmo de assinatura RSASSA_PKCS1_V1_5_SHA_256. Para validar esta assinatura você precisará da chave pública par dessa chave privada que foi gerada por nós.

Essa chave pública pode ser consultada no endpoint, passando o kid (o id da chave): v3/public_keys/:kid
Este kid é encontrado nos headers do token JWS que foi recebido.

Passo a Passo

  1. Nós geramos um JWT com o payload do webhook
  2. Assinamos o JWT com nossa chave privada, gerando um JWS
  3. Criptografamos o JWS utilizando a chave pública do cliente, gerando um JWE (cujo payload é o JWS)
  4. Enviamos o webhook para o cliente, contendo o JWE no corpo da requisição
  5. O cliente descriptografa o JWE utilizando sua chave privada (gerada na seção de Criptografia), o corpo desse JWE é o JWS da etapa 2
  6. Com o JWS, o cliente deve buscar o id da chave pública utilizada para assiná-lo, este id estará contido no campo kid dos headers do JWS
  7. O cliente consulta nossa API (v3/public_keys/:kid) e obtém a chave pública que consegue verificar a assinatura
  8. O cliente verifica a assinatura do JWS confirmando a autenticidade da mensagem
  9. O cliente decodifica o corpo do JWS, que contém o evento de fato em formato JSON

Codificando a descriptografia e validação da assinatura

Boa parte das linguagens possuem bibliotecas para efetuar as etapas necessárias, uma das bibliotecas mais usadas é a JOSE, que possuí implementação em diversas linguagens.

Segue abaixo um exemplo de código em Ruby que efetua a descriptografia do JWE, valida a assinatura do JWS e retorna o evento do webhook em JSON já parseado:

require 'jwt'
require 'net/http'
require 'json'
require 'openssl'

def find_public_key(kid)
  url = URI("https://sandbox.trampol.in/v3/public_keys/:kid")

  http = Net::HTTP.new(url.host, url.port)
  http.use_ssl = true

  request = Net::HTTP::Get.new(url)
  request["x-api-key"] = ENV['MY_API_KEY']

  response = http.request(request)
  return JSON.parse(response.read_body)["pem"] if response.is_a?(Net::HTTPSuccess)
end

def decrypt_and_verify(encrypted_data)
  # Importando a chave privada (usada pra descriptografar)
  pem_private_key = ENV['MY_WEBHOOK_PRIVATE_KEY']
  key = OpenSSL::PKey::RSA.new(pem_private_key)
  # Importa a chave para biblioteca JOSE (que será necessária para descriptografar)
  jose_key = JOSE::JWK.from_key(key)
  # Descriptogragando o JWE e pegando seu payload (que é o JWS)
  jws_token = JOSE::JWE.block_decrypt(jose_key, encrypted_data).first
  # Decodificando e parseando os headers do JWS
  jws_headers = JSON.parse(Base64::decode64(jws_token.split(".")[0]))

  # Como o endpoint de webhooks é público, é necessário validar a assinatura do token JWS, garantindo que foi enviando pela Stone
  # 1. Buscar a chave pública par da chave privada que foi usada para assinar
  pem_public_key = find_public_key(headers["kid"])
  # 2. Importando a chave para o OpenSSL
  rsa_public = OpenSSL::PKey::RSA.new(pem_public_key)
  # 3. Decodificando e validando a assinatura do token
  decoded_token = JWT.decode token, rsa_public, true, { algorithm: 'RS256' }
  payload = decoded_token.first # extraindo o corpo do token (já decodificado)

  # Essa variável payload que contém o evento do webhook, com os dados necessários para tratamento
  return payload
end

received_jwe_token = "eYij..."
decrypted_webhook_event_payload = decrypt_and_verify(received_jwe_token)

Exemplo do Passo a Passo

Para um evento de kyc_created (os dados do resource foram suprimidos para maior clareza):

O JWE (encrypted JWT) abaixo será enviado no body da requisição POST de notificação para a URL especificada pelo cliente (com content-type text/plain)

eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAiLCJraWQiOiJFOXVMNXVWbndKd25yUWtOTTBPWUFld053eW5ya1lpNFRDcC1qX2UxcXFzIn0.m7vM2NfwTMYF47FZB-OS1dWcOIfCbLn7E72lM0defgTJSpeqhMlR4JSdx1RIWhegdfa3cxQQn4IzyPLVH5RoTdNeMxttkghEvulQs2KEnl6m5nZB69HjVt98l9MiZM4z_v0YjTdd0kQ1SqVGCdqT3_ePDtqTatmY6oxZU81juKHgTbawo6_MZJWoFyc51I85UGtYnfyk3-xrumpSJ7s3rGd1D0FF32Yq22CNnKqi-8u9CBGLSK5Dd1MQOyi10s6u-vXRakeiCQXurWAtU0i6b_8eoM3w4-SyRD1mHwHfyiBmX_sPUQSSrH2HuZr7lgEit1YqaQMEge8h9lWse2ygag.bPWqtT08--0OQmI17gbK_w.5HUOTqCbJYsrzErCCFplqyKUetdjYZt5ZotGdr7_pjCTSH9S9NIVvrPfU2F7v0nDwNoYjymzqOWgjSEVrdhXSsyLwCYOGTCMrnBfPdNTJ81Zn4osKfWJ5Uqx60wuHmBMtw81PgroQS0-kDb0yfcwMtZGYiZkbT6hpi68-T7PZFKq9D3-gmBoY_lksxFb2IqKLbgP5JXCn2SzAmzX-yL_JVO0x8qhsGqj7JbTcd08Sxi68zOkkFV0WPZ3Fvuk46Q2vOLb3jeOl2YHjNQmHiOk4CGrSR5BXCeXEO0RAQiMO50u8KTIw-cmjiGrYIsUGcXJUGT61Oi_6hVOOaNsRqjsiagaOYQdpd4CSz121TysD9J7TlXDItROycJJKXISgZN04mcyXpi-11Oe9AG3V4CibRSfT-2wp7taGy4033svfQi85vhZE5ReFQeAvV2Q1WEpcOsVKE4bbzvA5cVf9J5GWXhathzp_7x_4Q46EhC-XmMXrTK7oMp1S64yiDVp7EoU6HE0nAlrkC0x_NWyI5ePtxUR93cv47yV-icHrpVSTxidvJJABf0g_OyhJFy5mJvwy0uJY92KJ-niFY_GbqcGl5qZD7hIetQP9hIayZL587BKz7YFD3t7GWe07AFCIjlQux8BNGKMXxPcdE4RoU3w54iWulUFHpR8C-D55oEELHM4E_1vQ-_vxnVQDM4EMkTMbbbu2utqxtyQ80oetkYNGVoiZtSX8XJCMuiDrlxYfj3hNCyzlL0thOkg5M2kiTBMhSkfrNGPmwKvfrENh3mDP8KOO7uUyaSICkNWpi6-oOuRqsKrxzC0LVgrxvfPwPSAi1XMMPYXgWbhlbRrCBa8oNGk5yLLn9qo28zPZcKhMCwoxHIVZINxm2V_1gXJzVpAb6hygjMbsUvcPFNvifEHuoKHqGYTHfuRVnNgaJghEH0.lt-GshGPR9MXZEmjnCT9EA

Ao decodificar esse JWE usando a chave privada do cliente obtém-se um JWS, como por exemplo o especificado abaixo:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjcwMDdlNzRiLTMxM2QtNGJkZi05MmIxLWVkN2ZjYTJhMGM5ZiJ9.eyJpZCI6IjViYWNmNWQ2LWUyYzctNDk4NS04MGY2LTRiNmEzOGI4OWZmZCIsImV2ZW50X3R5cGUiOiJreWNfY3JlYXRlZCIsInJlc291cmNlX3R5cGUiOiJreWMiLCJyZXNvdXJjZV9pZCI6MTE3MSwicmVzb3VyY2UiOnsiaWQiOjExNzEsImFjY291bnRfaWQiOjIzNCwic3RhdHVzIjoiYXBwcm92ZWQiLCJzdGF0dXNfcmVhc29uIjoib2siLCJvcGVyYXRvciI6eyJuYW1lIjoiSm9zZSBQZW50ZWFkbyBkYSBTaWx2YSIsImNwZiI6IjIzOTI5MjU4MDEzIiwiZGF0ZV9vZl9iaXJ0aCI6IjE5NjItMDktMTAiLCJwaG9uZV9udW1iZXIiOiI1NTIxMTIzNDU2NzgiLCJhdHRhY2hlZF9kb2N1bWVudF90eXBlIjoiY25oIn0sIm93bmVyIjp7Im5hbWUiOiJKb3NlIFBlbnRlYWRvIGRhIFNpbHZhIiwiZG9jdW1lbnQiOiIyMzkyOTI1ODAxMyIsImRvY3VtZW50X3R5cGUiOiJjcGYiLCJkYXRlX29mX2JpcnRoIjoiMTk2Mi0wOS0xMCJ9LCJhZGRyZXNzIjp7ImNvdW50cnkiOm51bGwsInN0YXRlIjoiUmlvIGRlIEphbmVpcm8iLCJjaXR5IjoiUmlvIGRlIEphbmVpcm8iLCJuZWlnaGJvcmhvb2QiOiJNYWR1cmVpcmEiLCJzdHJlZXQiOiJydWEgZG8gbGFkbyIsInN0cmVldF9udW1iZXIiOiIxNSIsInBvc3RhbF9jb2RlIjoiMjIzMzMwMDAiLCJkb29yIjpudWxsfX0sImlhdCI6MTYzOTc3NDU3NSwiZXhwIjoxNjM5ODYwOTc1fQ.DQSlM2UC4lvyPnUQgKfNMC5x2dcI5nkl7juU2-nHBj5INoTtELtvN7Hr6T_XgPPX0wEkmjHxjBJmcInsi1JPoh5WABAjigFMBgKJB5il8MVvONP3Xi1SwdC0k6d-Rv0jvCNAuKC2terDC1oMZUcYUs9lbUAgKL9Y-cDwqNLJkgfXJdKm5DQwMDtr98fcdulY-sTS68Muwc_8PoLQtS09Mle23E7gSYaa7goqaIcA3tqRL_GL1BaBaXPO0jryxiijmdWuxC2ykxbjZhxjLIOMp3jlE4rN9J8OUj2NBlCaSI4uyORMXa7y3NuDsK7daZ2U4lDXGsVLKiMyO31QAukOkQ

Ao decodificar (base64) o cabecalho desse JWS, é obtido o seguinte:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "7007e74b-313d-4bdf-92b1-ed7fca2a0c9f"
}

E corpo (pode ser diferente de acordo com o tipo do evento):

{
  "id": "5bacf5d6-e2c7-4985-80f6-4b6a38b89ffd",
  "event_type": "kyc_created",
  "resource_type": "kyc",
  "resource_id": 1171,
  "resource": {
    "id": 1171,
    "account_id": 234,
    "status": "approved",
    "status_reason": "ok",
    "operator": {
      "name": "Jose Penteado da Silva",
      "cpf": "23929258013",
      "date_of_birth": "1962-09-10",
      "phone_number": "21912345678",
      "attached_document_type": "cnh"
    },
    "owner": {
      "name": "Jose Penteado da Silva",
      "document": "23929258013",
      "document_type": "cpf",
      "date_of_birth": "1962-09-10"
    },
    "address": {
      "country": "BR",
      "state": "RJ",
      "city": "Rio de Janeiro",
      "neighborhood": "Madureira",
      "street": "rua do lado",
      "street_number": "15",
      "postal_code": "22333000",
      "door": "apt 504"
    }
  },
  "iat": 1639774575,
  "exp": 1639860975
}

Ao consultar a chave publica em v3/public_keys/7007e74b-313d-4bdf-92b1-ed7fca2a0c9f você obtém a resposta:

{
    "kid": "7007e74b-313d-4bdf-92b1-ed7fca2a0c9f",
    "pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPXHxsujm9cuDT+4WM05\nQm3SfjZCiXYI4gfFtNnLMZSCO8x8q411pVaDYkYZncLB4Nfz2+05SFZQmvul5k9M\nnkxzBzDVKpVlbE4kDO4OcBumulbaqRGwGvlgqhTJWWsMVkATHmS+uvA4nsMVW1Cf\nkyWSPMACypBdiznZGYjDRkoVlqbWH+1mc/Et/8JqmA1GOfT78c+9ztzHjZm2dVbU\nLaYtI30q7X3zk2pPgSPOZeObPa5HEVTBV2rpj8+6/98y/EJg3sM0HZN8aKxnuZsZ\nsDv9Ov+gD7OiPMW3U2uGtbZyfPnE17cdDB4zjAs+M0grwS13QzhoymT/VrrHeX5Z\n9wIDAQAB\n-----END PUBLIC KEY-----"
}

Os \n representam quebras de linha (ao usar ferramentas para debug pode ser necessário substituir por quebras de linha)

Conforme a resposta acima, a chave pública no formato PEM a ser usada para verificar a assinatura é:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPXHxsujm9cuDT+4WM05
Qm3SfjZCiXYI4gfFtNnLMZSCO8x8q411pVaDYkYZncLB4Nfz2+05SFZQmvul5k9M
nkxzBzDVKpVlbE4kDO4OcBumulbaqRGwGvlgqhTJWWsMVkATHmS+uvA4nsMVW1Cf
kyWSPMACypBdiznZGYjDRkoVlqbWH+1mc/Et/8JqmA1GOfT78c+9ztzHjZm2dVbU
LaYtI30q7X3zk2pPgSPOZeObPa5HEVTBV2rpj8+6/98y/EJg3sM0HZN8aKxnuZsZ
sDv9Ov+gD7OiPMW3U2uGtbZyfPnE17cdDB4zjAs+M0grwS13QzhoymT/VrrHeX5Z
9wIDAQAB
-----END PUBLIC KEY-----

Com a assinatura validada é seguro utilizar os dados do evento para fazer os devidos tratamentos no seu sistema.