Webhooks
Í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
- Webhook de KYC
- Webhook de Conta de Pagamento
- Webhook de Chave Pix
- Webhook de Agendamento
- Webhook de Cartão
- Webhook de rastreamento do Cartão
Webhook de Transação
Os eventos de um webhook de transação são os seguintes:
event_type | descrição |
---|---|
transaction_created | Quando uma transação é criada |
transaction_approved | Quando o status de uma transação muda para aprovado |
transaction_denied | Quando 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_type | descrição |
---|---|
kyc_created | Quando um KYC é criada |
kyc_approved | Quando o status de um KYC muda para aprovado |
kyc_denied | Quando 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_type | descrição |
---|---|
account_created | Quando uma conta de pagamento é criada |
account_approved | Quando o status de uma conta de pagamento passa para aprovada |
account_denied | Quando 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_type | descrição |
---|---|
pix_key_created | Quando uma chave pix é criada |
pix_key_approved | Quando a chave pix é aprovada |
pix_key_denied | Quando 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_type | descrição |
---|---|
scheduled_transaction_created | Quando um agendamento é criado |
scheduled_transaction_approved | Quando o agendamento é aprovado |
scheduled_transaction_denied | Quando 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_type | descrição |
---|---|
card_created | Quando um cartão é criada |
card_waiting_first_activation | Quando o cartão foi emitido e está aguardando o usuário receber para ativar |
card_active | Quando o cartão é ativado |
card_blocked | Quando o cartão é bloqueado |
card_canceled | Quando 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_type | descrição |
---|---|
card_delivery_status_in_production | Período entre a aprovação do cartão e a saída do cartão na transportadora |
card_delivery_status_in_transit | Quando um cartão está em rota de entrega pela transportadora |
card_delivery_status_delivered | Quando o cartão é entregue na casa do cliente |
card_delivery_status_resend | Quando está repetindo a tentativa de entrega |
card_delivery_status_other_problem | Quando houve um problema na entrega do cartão |
card_delivery_status_delivery_canceled | Quando 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:
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
- Nós geramos um JWT com o payload do webhook
- Assinamos o JWT com nossa chave privada, gerando um JWS
- Criptografamos o JWS utilizando a chave pública do cliente, gerando um JWE (cujo payload é o JWS)
- Enviamos o webhook para o cliente, contendo o JWE no corpo da requisição
- O cliente descriptografa o JWE utilizando sua chave privada (gerada na seção de Criptografia), o corpo desse JWE é o JWS da etapa 2
- 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 - O cliente consulta nossa API (
v3/public_keys/:kid
) e obtém a chave pública que consegue verificar a assinatura - O cliente verifica a assinatura do JWS confirmando a autenticidade da mensagem
- 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.
Updated 12 months ago