.. _developer-guide: Guia de desenvolvimento ####################### Este guia mostra os passos necessários para o desenvolvimento das integrações com o iD. A comunicação entre os sistemas emissores, verificadores e carteiras de usuários está representada na figura abaixo. .. figure:: /_images/cpqdidAgents.png :alt: text :align: center :width: 75% Plataforma iD e sistemas externos Toda comunicação que envolve a troca de credenciais ocorre através de um protocolo padronizado `DIDComm `_. Este protocolo usa WebSockets para estabelecer um canal bidirecional entre as partes. A implementação desse protocolo é realizada por componentes de software chamados **Agentes**. Todo agente possui uma **carteira**, que é uma base de dados onde são armazenadas as chaves privadas usadas para criptografar as mensagens. As credenciais de usuário também são armazenadas na carteira do agente do portador. Os agentes utilizam APIs para estabelecer conexões entre si e realizar transações sobre as credenciais. Para utilizar os serviços dos agentes do iD, é necessário estar cadastrado no portal `iD Empresas `_ (ver :ref:`Primeiros Passos `) e possuir um token de acesso. Ambientes ********* Staging ======= O ambiente de *staging* possui os seguintes endereços: * Portal iD Empresas: ``_ * Documentação das APIs: ``_ * URL base de API: \https://staging-id.cpqd.com.br/api/ Endpoints ========= As APIs são organizadas conforme o tipo de agente: emissor, verificador e portador. Para cada um, temos dois endpoints: o agente e o serviço de gestão AFA (advanced function agent). +------------------------+------------+--------------------------------------------------------+ | | AFA | \https://staging-id.cpqd.com.br/api/hub-issuer/afa | | Hub do Emissor +------------+--------------------------------------------------------+ | | Agente | \https://staging-id.cpqd.com.br/api/hub-issuer/agent | +------------------------+------------+--------------------------------------------------------+ | | AFA | \https://staging-id.cpqd.com.br/api/hub-verifier/afa | | Hub do Verificador +------------+--------------------------------------------------------+ | | Agente | \https://staging-id.cpqd.com.br/api/hub-verifier/agent | +------------------------+------------+--------------------------------------------------------+ | | AFA | \https://staging-id.cpqd.com.br/api/hub-holder/afa | | Hub do Portador +------------+--------------------------------------------------------+ | | Agente | \https://staging-id.cpqd.com.br/api/hub-holder/agent | +------------------------+------------+--------------------------------------------------------+ Para maiores detalhes consulte a documentação detalhada no `swagger `_. Obter o token de acesso do agente V1 ************************************* Para utilizar as APIs do agente é necessário obter um token de acesso. Utilize o **Wallet Name**, disponível no menu "Minha carteira" no portal `iD Empresas `_ , e, caso já possua, a **API Key** previamente fornecida para realizar requisições ao serviço AFA. **Passo 1.** Para obter o *token* de acesso para o agente, use a chamada ao serviço AFA: :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-issuer/afa/v2/wallet/$WALLET_NAME/token' \ --header 'ApiKey: $API_KEY' Onde: * **WALLET_NAME**: nome da carteira * **API_KEY**: chave de autorização da carteira A requisição possui corpo vazio. Em caso de sucesso, irá retornar o token de acesso no formato JSON conforme abaixo: .. code-block:: json { "token": "" } Endpoints V2 ============ As APIs V2 são organizadas conforme o tipo de agente: emissor e verificador. Cada agente possui seu endpoint, e o emissor também possui o serviço de autenticação (Auth Manager). +------------------------+------------------------------+---------------------------------------------------------------------------+ | | Agente V2 | \https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2 | | Hub do Emissor +------------------------------+---------------------------------------------------------------------------+ | | Auth Manager | \https://staging-id.cpqd.com.br/api/auth-manager/v1 | +------------------------+------------------------------+---------------------------------------------------------------------------+ | | | | | Hub do Verificador | Agente V2 | \https://staging-id.cpqd.com.br/api/hub-verifier/agent/ | | | | | +------------------------+------------------------------+---------------------------------------------------------------------------+ Obter o token de acesso do agente V2 ************************************ Para utilizar a API do agente emissor V2 é necessário obter um token de acesso. Para isso, é necessário utilizar os campos **Wallet Name** e **Secret** do menu “Minha carteira” no portal `iD Empresas `_ . **Passo 1.** Para obter o *token* de acesso para o agente, use a chamada ao serviço Auth Manager: :: curl -X POST 'https://staging-id.cpqd.com.br/api/auth-manager/v1/token' \ --data '{ "wallet_name": "$WALLET_NAME", "password": "$SECRET" }' Onde: * **WALLET_NAME**: nome da carteira * **SECRET**: senha da chave de autorização da carteira Em caso de sucesso, irá retornar o token de acesso no formato JSON conforme abaixo: .. code-block:: json { "token": "" } Gerenciamento de conexões ************************* Antes de executar uma transação com credenciais, é necessário estabelecer uma conexão entre o agente do emissor ou verificador, e o agente do portador. Para isso, deve-se criar um convite no formato de URL e compartilhá-lo com os usuários. Esse link pode ser transformado em um **QR Code** usando ferramentas como o `QR Code Generator `_ para facilitar a leitura por câmeras e celulares. .. note:: Caso o QR Code fique muito 'denso', você pode usar uma ferramenta como a `URL Shortener `_ para diminuir o tamanho da URL. Gerar convite de conexão *out-of-band* ====================================== Uma das formas de criar um convite de conexão é a API */out-of-band*. Para isso, use a seguinte chamada de API. :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/out-of-band/create-invitation?multi_use=$MULTI_USE' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "alias": "alias for the connection", "my_label": "label for the connection invitation", "metadata": {}, "handshake_protocols": [ "https://didcomm.org/connections/1.0" ] }' Opcionalmente, preencha os campos: * **MULTI_USE**: (opcional) se igual a “true”, indica que o convite pode ser utilizado várias vezes, abrindo novas conexões a cada leitura. Caso contrário, ele perde a validade após a primeira leitura (valor padrão = false) * **alias**: (opcional) rótulo arbitrário para a conexão * **my_label**: (opcional) rótulo arbitrário para o convite * **metadata**: (opcional) objeto que será anexado à conexão, utilizado para transportar informações de contexto associado ao convite. A informação não é transferida para o outro lado da conexão A resposta é semelhante ao exemplo abaixo: .. code-block:: json { "state":"initial", "trace":false, "invi_msg_id":"fa1bb934-17c9-4507-81f3-0e94d33422ec", "oob_id":"a1e2bc29-662c-4699-b30c-67d3bfa64e37", "invitation":{ "@type":"https://didcomm.org/out-of-band/1.1/invitation", "@id":"fa1bb934-17c9-4507-81f3-0e94d33422ec", "label":"label for the connection invitation", "handshake_protocols":[ "https://didcomm.org/connections/1.0" ], "services":[ { "id":"#inline", "type":"did-communication", "recipientKeys":[ "did:key:z6MkqkhAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST#z6Mkqk hAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST" ], "serviceEndpoint":"https://staging-id.cpqd.com.br/api/hub-issuer/ agent-didcomm" } ] }, "invitation_url":"https://staging-id.cpqd.com.br/api/hub-issuer/agent-didcomm?oob=eyJAdHlwZSI6ICJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzEuMS9pbnZpdGF0aW9uIiwgIkBpZCI6ICJmYTFiYjkzNC0xN2M5LTQ1MDctODFmMy0wZTk0ZDMzNDIyZWMiLCAibGFiZWwiOiAiY3BxZC1pc3N1ZXIiLCAiaGFuZHNoYWtlX3Byb3RvY29scyI6IFsiaHR0cHM6Ly9kaWRjb21tLm9yZy9jb25uZWN0aW9ucy8xLjAiXSwgInNlcnZpY2VzIjogW3siaWQiOiAiI2lubGluZSIsICJ0eXBlIjogImRpZC1jb21tdW5pY2F0aW9uIiwgInJlY2lwaWVudEtleXMiOiBbImRpZDprZXk6ejZNa3FraEFKZk10R0NpcEppaEVoZE1UckpGclM3emF3UUFIdFJKdEZ1UHpubVNUI3o2TWtxa2hBSmZNdEdDaXBKaWhFaGRNVHJKRnJTN3phd1FBSHRSSnRGdVB6bm1TVCJdLCAic2VydmljZUVuZHBvaW50IjogImh0dHBzOi8vc3RhZ2luZy1pZC5jcHFkLmNvbS5ici9hcGkvaHViLWlzc3Vlci9hZ2VudC1kaWRjb21tIn1dfQ==", "alias":"alias for the connection" } Observe os campos na resposta: * **invi_msg_id**: contém o identificador da mensagem do convite * **invitation**: objeto do convite, que deve ser compartilhado com o usuário * **invitation_url**: URL do convite, que contém o objeto do convite codificado em base64 Quando o usuário aceitar o convite e a conexão for estabelecida, o *webhook* do agente receberá uma mensagem no tópico */connections* contendo os dados da conexão. Descartar convite de conexão ============================ Para descartar um convite de conexão é necessário usar o **invi_msg_id** na seguinte chamada de API: :: curl -X DELETE 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/out-of-band/invitations/$INVI_MSG_ID' \ --header 'Authorization: Bearer $TOKEN' A resposta não contém corpo, mesmo em caso de sucesso. Consultar conexões ================== Para verificar as conexões existentes do agente, use a seguinte chamada de API: :: curl -X GET 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/connections' \ --header 'Authorization: Bearer $TOKEN' Os seguintes parâmetros da URL podem ser utilizados para filtrar a busca, de forma opcional: * **alias**: valor arbitrário definido no convite * **state**: estado da conexão (valores possíveis: *abandoned*, *active*, *completed*, *error*, *init*, *invitation*, *request*, *response*, *start*) A resposta é semelhante ao exemplo abaixo: .. code-block:: json { "results":[ { "created_at":"2023-06-02T12:32:56.936708Z", "updated_at":"2023-06-02T12:32:57.525851Z", "invitation_msg_id":"lp8e5610-0799-443d-90b2-6656dbcd89f8", "connection_id":"7i8e5880-4e47-4be2-9d2b-561b06e8ff23", "their_did":"EzqgxguBfLpXBz9eUkY4l7", "their_role":"inviter", "their_label":"issuer", "connection_protocol":"connections/1.0", "my_did":"2iMyPY5xcNxHzcR5HtLWd4", "state":"active", "rfc23_state":"completed", "request_id":"633335wc-9426-4217-bf21-43c12b5y67f3", "accept":"auto", "invitation_mode":"once", "invitation_key":"97jLje2Ca6T4HadXwhVif4TmeESXpWYcNqoWPPRX8N1o", "routing_state":"none" } ] } Observe os campos: * **connection_id**: identificador da conexão * **status**: status da conexão * **their_label**: nome do agente conectado .. note:: Para realizar uma transação com um agente, a conexão deve possuir status “active”. Qualquer outro status indica que ela não está pronta para uso. Remover conexões ================ Para remover uma conexão é necessário usar o **connection_id** na seguinte chamada de API: :: curl -X DELETE 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/connections/$CONNECTION_ID' \ --header 'Authorization: Bearer $TOKEN' A resposta não contém corpo, mesmo em caso de sucesso. Aceitar convite de conexão ========================== Para iniciar uma conexão com um agente emissor ou verificador, o usuário precisa receber um convite de conexão. Esse convite pode ser lido de uma URL ou um QR Code, pelo aplicativo da carteira do usuário. A URL contém todas as informações necessárias para estabelecer a conexão. **Passo 1.** Faça a leitura da URL do convite. Observe o parâmetro *c_i* ou *oob* e faça um decode em base64 do seu valor. Isso irá gerar o objeto com os dados do convite. Exemplo de URL: :: https://staging-id.cpqd.com.br/api/hub-issuer/agent-didcomm?oob=eyJAdHlwZSI6ICJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzEuMS9pbnZpdGF0aW9uIiwgIkBpZCI6ICJmYTFiYjkzNC0xN2M5LTQ1MDctODFmMy0wZTk0ZDMzNDIyZWMiLCAibGFiZWwiOiAiY3BxZC1pc3N1ZXIiLCAiaGFuZHNoYWtlX3Byb3RvY29scyI6IFsiaHR0cHM6Ly9kaWRjb21tLm9yZy9jb25uZWN0aW9ucy8xLjAiXSwgInNlcnZpY2VzIjogW3siaWQiOiAiI2lubGluZSIsICJ0eXBlIjogImRpZC1jb21tdW5pY2F0aW9uIiwgInJlY2lwaWVudEtleXMiOiBbImRpZDprZXk6ejZNa3FraEFKZk10R0NpcEppaEVoZE1UckpGclM3emF3UUFIdFJKdEZ1UHpubVNUI3o2TWtxa2hBSmZNdEdDaXBKaWhFaGRNVHJKRnJTN3phd1FBSHRSSnRGdVB6bm1TVCJdLCAic2VydmljZUVuZHBvaW50IjogImh0dHBzOi8vc3RhZ2luZy1pZC5jcHFkLmNvbS5ici9hcGkvaHViLWlzc3Vlci9hZ2VudC1kaWRjb21tIn1dfQ== Objeto decodificado por base64: .. code-block:: json {"@type":"https://didcomm.org/out-of-band/1.1/invitation","@id":"fa1bb934-17c9-4507-81f3-0e94d33422ec","label":"cpqd-issuer","handshake_protocols":["https://didcomm.org/connections/1.0"],"services":[{"id":"#inline","type":"did-communication","recipientKeys":["did:key:z6MkqkhAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST#z6MkqkhAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST"],"serviceEndpoint":"https://staging-id.cpqd.com.br/api/hub-issuer/agent-didcomm"}]} Observe o campo **@type**, ele indica o tipo de conexão que deve ser feita. **Passo 2.** Se o tipo de conexão for **didcomm.org/out-of-band/1.1/invitation**, utilize a API *out-of-band* para aceitar o convite e iniciar a conexão. Use o objeto do convite no corpo da requisição. :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-holder/agent/out-of-band/receive-invitation' \ --header 'ApiKey: $API_KEY' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "@type":"https://didcomm.org/out-of-band/1.1/invitation", "@id":"fa1bb934-17c9-4507-81f3-0e94d33422ec", "label":"cpqd-issuer", "handshake_protocols":[ "https://didcomm.org/connections/1.0" ], "services":[ { "id":"#inline", "type":"did-communication", "recipientKeys":[ "did:key:z6MkqkhAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST#z6MkqkhAJfMtGCipJihEhdMTrJFrS7zawQAHtRJtFuPznmST" ], "serviceEndpoint":"https://staging-id.cpqd.com.br/api/hub-issuer/agent-didcomm" } ] }' A resposta é semelhante ao exemplo abaixo: .. code-block:: json { "state":"deleted", "created_at":"2024-05-13T13:46:48.822907Z", "updated_at":"2024-05-13T13:46:48.822907Z", "trace":false, "oob_id":"1668f2d5-e001-400d-84eb-000e00a84d1a", "invi_msg_id":"fa1bb934-17c9-4507-81f3-0e94d33422ec", "invitation":{ }, "connection_id":"f5ed4759-1904-42c3-8384-941ea11ebd25", "role":"receiver", "multi_use":false } Observe na resposta o campo **connection_id**. Ele indica o identificador da conexão estabelecida entre os agentes e é necessário para chamadas de API que dependem da conexão. .. _devguide_implement_webhook: Desenvolvimento de webhooks *************************** Um webhook é um serviço que funciona como um *listener* dos agentes do iD, recebendo notificações sobre eventos de uma transação em andamento. Ele deve ter um endereço público (URL) e opcionalmente, uma API Key para autenticação de acesso. Essas informações devem ser registradas no portal `iD Empresas `_ , associado à sua carteira, para que os agentes possam enviar notificações de eventos e status das transações relacionadas a credenciais (ver :ref:`Configuração de Webhooks `). O agente enviará requisições HTTP REST para os webhooks cadastrados, adicionando o caminho */topic/* na URL, conforme o tipo de transação. Se definido uma API-Key para autenticação, o agente enviará o header **X-Api-Key** na requisição. .. note:: A implementação do webhook deve responder às mensagens recebidas com o status HTTP 200 OK o mais rápido possível, para evitar o bloqueio desnecessário de recursos no agente. Atrasos na resposta do webhook causam bloqueios na transação e degradação no desempenho do agente, prejudicando outros usuários. Se ocorrer um erro no webhook que resulte em uma resposta com status HTTP 500, a transação em execução no agente será interrompida. A seguir, estão descritos os tópicos mais relevantes que devem ser implementados pelo webhook, quando aplicável. POST /topic/connections ======================= Este é o tópico para os eventos de conexão entre agentes. As mensagens recebidas neste tópico possuem um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "state":"active", "created_at":"2024-05-29T13:16:43.740444Z", "updated_at":"2024-05-29T13:16:44.012376Z", "connection_id":"26da8131-5f2a-4f08-a439-606a34e97be7", "my_did":"B7E1svNn3rjEQ4wZoKx4Se", "their_did":"7TVNrbFdijB9RWMHYTFJE1", "their_label":"bob", "their_role":"invitee", "connection_protocol":"connections/1.0", "rfc23_state":"completed", "invitation_key":"7BjFYbGJuh6B5FTZnMkxiKnbPvnmAaYEEPcha4PgDBe2", "routing_state":"none", "accept":"auto", "invitation_mode":"once" } Observe os campos: * **connection_id**: identificador da conexão * **status**: status da conexão * **their_label**: rótulo do convite identificando o agente * **invitation_msg_id**: identificador do convite Utilize o valor de **connection_id** nas requisições que exigem conexão ativa entre agentes, para emissão e prova de credenciais. O campo **status** pode ter os seguintes valores: .. list-table:: :widths: 20 80 :header-rows: 1 * - State - Descrição * - invitation - *Convite de conexão criado*. Indica que um agente criou um convite de conexão para outro agente, gerando o identificador da conexão (*connection_id*), o conteúdo do convite e a URL do convite (*invitation_url*). * - request - *Pedido de conexão enviado*. Indica que uma resposta a um convite de conexão foi enviado pelo portador ao autor do convite. * - response - *Resposta de conexão enviada*. Indica que uma resposta ao pedido de conexão foi enviada pelo autor do convite ao portador. * - active - *Conexão ativa*. Indica que o convite foi aceito e que a conexão está ativa para troca de mensagens. * - completed - Indica que a conexão é válida e está pronta para uso. A figura a seguir ilustra o fluxo de eventos quando um usuário aceita um convite de conexão, pelos webhooks dos agentes portador e emissor. .. figure:: /_images/webhook_connections.png :alt: text :align: center :width: 100% Figura 1: Eventos do fluxo de conexão. POST /topic/out-of-band ======================= O protocolo *out-of-band* gera mensagens para este tópico. Através dele, o webhook de quem criou o convite recebe o identificador da conexão criada quando o convite for aceito pelo outro agente. Simultaneamente, o tópico **/topic/connections** também é acionado quando a conexão é criada. As mensagens recebidas neste tópico são semelhantes ao exemplo abaixo: .. code-block:: json { "state":"await-response", "created_at":"2024-05-13T17:10:18.252500Z", "updated_at":"2024-05-13T17:10:18.252500Z", "trace":false, "oob_id":"08f75e7b-a3ef-4cfc-a2d8-58b8ac43d2ad", "invi_msg_id":"b77631a4-6138-4b7d-812f-a67a1bc655db", "invitation":{ "@type":"https://didcomm.org/out-of-band/1.1/invitation", "@id":"b77631a4-6138-4b7d-812f-a67a1bc655db", "label":"alice", "handshake_protocols":[ "https://didcomm.org/connections/1.0" ], "services":[ { "id":"#inline", "type":"did-communication", "recipientKeys":[ "did:key:z6Mkei8GLd4kf7gieKmcJrWWGZGmvFju6seJvZ2ZNphuN3Uc#z6Mkei8GLd4kf7gieKmcJrWWGZGmvFju6seJvZ2ZNphuN3Uc" ], "serviceEndpoint":"http://alice-agent:9002" } ] }, "connection_id":"4853b392-3f3a-4e03-baa5-9f9841550d37", "our_recipient_key":"FsDkNpKKaCFXpvudHYfRTin6gU3gzPxEY7dYYjtSphE", "role":"sender", "multi_use":false } Observe o campo **connection_id**, ele contém o identificador da conexão que foi criada. POST /topic/basicmessages ========================= Este é o tópico para os eventos de mensagens genéricas de texto, que podem ser trocados entre a aplicação e o usuário, usando a conexão ativa entre os agentes. As mensagens recebidas neste tópico possuem um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "@id": "123456780", "@type": "https://didcomm.org/basicmessage/1.0/message", "~l10n": { "locale": "en" }, "sent_time": "2019-01-15 18:42:01Z", "content": "This is a sample message." } POST /topic/issue_credential_v2_0 ================================= Este é o tópico para os eventos de emissão de credenciais. As mensagens recebidas neste tópico possuem um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "role":"issuer", "initiator":"self", "auto_offer":true, "auto_remove":false, "thread_id":"5661591f-f715-4787-adf4-1ec2eeb77271", "state":"offer-sent", "created_at":"2024-05-13T18:05:27.111632Z", "updated_at":"2024-05-13T18:05:27.111632Z", "cred_ex_id":"bcea07be-6d07-461c-946f-a11d3e7b9013" } Observe os campos: * **connection_id**: identificador da conexão * **state**: estado da transação * **cred_ex_id**: identificador da transação Os dados da oferta ou pedido de credencial podem ser acessados pelo identificador da transação cred_ex_id na API **/issue-credentials-2.0**. O campo **state** pode ter os seguintes valores: .. list-table:: :widths: 20 80 :header-rows: 1 * - State - Descrição * - proposal_sent - *Proposta de emissão de credencial enviada*. Indica que uma proposta para emissão de credencial foi enviada pelo portador ao emissor. Isso pode ocorrer porque o portador iniciou o fluxo ou nos casos em que ele deseja realizar ajustes nos dados da credencial ofertada (após status *offer_received*). * - proposal_received - *Proposta de emissão de credencial recebida*. Indica que a proposta enviada pelo portador para emissão de uma credencial foi recebida pelo emissor. Isso pode ocorrer porque o portador iniciou o fluxo enviando uma proposta ou nos casos em que o portador deseja realizar ajustes nos dados da credencial ofertada (após status *offer_received* do portador). * - offer_sent - *Oferta de emissão enviada*. Indica que uma oferta para emissão de credencial foi enviada pelo emissor ao portador. Isso pode acontecer porque o emissor iniciou o fluxo ou após o recebimento de uma proposta de emissão (após status *proposal_received* do emissor). * - offer_received - *Oferta de emissão recebida pelo usuário*. Indica que a oferta para emissão de credencial enviada pelo emissor foi recebida pelo portador. Isso pode acontecer porque o emissor iniciou o fluxo enviando uma oferta ou após o recebimento da proposta de emissão enviada pelo portador (após status *proposal_sent* do portador). * - request_sent - *Requisição de emissão enviada*. Indica que uma requisição de emissão de credencial foi enviada pelo holder ao emissor. Isso significa que a oferta recebida pelo portador (*offer_received*) foi aceita. * - request_received - *Requisição de emissão recebida*. Indica que a requisição de emissão da credencial referente ao aceite da oferta pelo portador foi recebida pelo emissor. No ID, após o aceite da oferta pelo portador, o fluxo passa automaticamente por esse status. * - credential_issued - *Credencial emitida*. Indica que a credencial foi emitida e enviada pelo emissor ao portador. * - credential_received - *Credencial recebida*. Indica que a credencial emitida foi recebida e seus dados foram armazenados na carteira digital do portador. * - credential_acked - *Credencial reconhecida*. Indica que o fluxo de emissão de credencial foi concluído com sucesso e a credencial foi reconhecida como emitida e armazenada na carteira do holder. * - abandoned - *Credencial rejeitada*. Ocorre após recusa de proposta pelo emissor ou de oferta pelo portador. A figura a seguir ilustra o fluxo de eventos nos webhook dos agentes portador e emissor, quando um emissor envia uma oferta para um usuário. .. figure:: /_images/webhook_issue_credential.png :alt: text :align: center :width: 100% Figura 2: Eventos do fluxo de emissão de credencial. O diagrama abaixo representa os estados do fluxo de emissão que são enviados para os webhooks, conforme o evento. .. figure:: /_images/webhook_status_issue_credential.png :alt: text :align: center :width: 100% Figura 3: Máquina de estados do fluxo de emissão de credencial. Adicionalmente, o evento de **revogação** possui um conteúdo semelhando ao exemplo abaixo. Este evento tambémé enviado para **/topic/issuer_cred_rev**, conforme descrito na sequencia. .. code-block:: json { "state":"credential_revoked", "created_at":"2024-03-08T11:54:32.763787Z", "updated_at":"2024-03-08T12:17:43.033893Z", "trace":true, "credential_exchange_id":"f4519117-d58f-4886-b4d0-4e9d621ab1fc", "connection_id":"4b375ed9-b9c4-4ecf-804d-d7a76dd2a88e", "thread_id":"5828692b-db00-483a-803c-db3636bfa942", "initiator":"self", "role":"issuer", "credential_definition_id":"UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person", "schema_id":"UeaHZWGeYCx4wy96VE9LQo:2:person:1.0", "credential_proposal_dict":{ }, "credential_offer_dict":{ }, "credential_offer":{ }, "credential_request":{ }, "credential":{ "schema_id":"UeaHZWGeYCx4wy96VE9LQo:2:person:1.0", "cred_def_id":"UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person", "rev_reg_id":"UeaHZWGeYCx4wy96VE9LQo:4:UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person:CL_ACCUM:007946f3-a3ed-4a68-be43-c142b7aa58d0" }, "auto_offer":false, "auto_issue":true, "auto_remove":false, "revoc_reg_id":"UeaHZWGeYCx4wy96VE9LQo:4:UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person:CL_ACCUM:007946f3-a3ed-4a68-be43-c142b7aa58d0", "revocation_id":"1" } POST /topic/issuer_cred_rev =========================== Este é o tópico para os eventos de revogação de credenciais. As mensagens recebidas neste tópico possuem um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "state": "revoked", "created_at": "2024-03-08T12:01:51.541629Z", "updated_at": "2024-03-08T12:17:42.959648Z", "record_id": "b07ba2b5-f1df-4f2d-9e12-ea1718150130", "cred_ex_id": "f4519117-d58f-4886-b4d0-4e9d621ab1fc", "rev_reg_id": "UeaHZWGeYCx4wy96VE9LQo:4:UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person:CL_ACCUM:007946f3-a3ed-4a68-be43-c142b7aa58d0", "cred_def_id": "UeaHZWGeYCx4wy96VE9LQo:3:CL:990:person", "cred_rev_id": "1", "cred_ex_version": "1" } Observe os campos: * **state**: estado da transação * **cred_ex_id**: identificador da transação POST /topic/present_proof_v2_0 ============================== Este é o tópico para os eventos de prova de credenciais. As mensagens recebidas neste tópico possuem um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "role":"verifier", "initiator":"self", "auto_present":false, "auto_verify":true, "thread_id":"c9634376-705e-4abe-a149-a39061a1a7cf", "state":"request-sent", "trace":false, "created_at":"2024-05-13T18:35:04.810535Z", "updated_at":"2024-05-13T18:35:04.810535Z", "pres_ex_id":"b64f0bf8-410d-486a-aff6-5cee86ca4e82" } Observe os campos: * **connection_id**: identificador da conexão * **state**: estado da transação * **pres_ex_id**: identificador da transação, necessário para recuperar os dados do pedido/compartilhamento O campo **state** pode ter os seguintes valores: .. list-table:: :widths: 20 80 :header-rows: 1 * - State - Descrição * - request_sent - *Pedido de prova enviado*. Indica que um pedido de prova contendo os valores que precisam ser compartilhados e os predicados que precisam ser cumpridos foi enviado pelo verificador ao portador. * - request_received - *Pedido de prova recebido*. Indica que o pedido de prova enviado pelo verificador contendo os valores que precisam ser compartilhados e os predicados que precisam ser cumpridos foi recebido pelo portador. * - proposal_sent - *Proposta de prova enviada*. Indica que uma mensagem opcional foi enviada pelo portador para iniciar um processo de apresentação de prova junto a um verificador, ou após status *request_received*, quando o portador deseja enviar uma resposta propondo um formato de apresentação diferente. * - proposal_received - *Proposta de prova recebida*. Indica que a mensagem opcional enviada pelo portador para iniciar um processo de apresentação de prova, ou após status *request_received*, quando o portador deseja enviar uma resposta propondo um formato de apresentação diferente, foi recebida pelo verificador. * - presentation_sent - *Apresentação de credencial enviada*. Indica que o portador concordou com o pedido e uma resposta contendo as provas assinadas foi enviada ao verificador. * - presentation_received - *Apresentação de credencial recebida*. Indica que a resposta do portador contendo as provas assinadas foi recebida pelo verificador. * - verified - *Credencial verificada*. Indica que o processo de verificação foi concluído com o consentimento do portador. Nesse status, o verificador consegue acesso aos dados da prova. * - presentation_acked - *Confirmação do recebimento da apresentação*. Indica que o verificador concluiu a execução da prova. * - abandoned - *Pedido ou proposta abandonada*. Ocorre quando o verificador ou o usuário sinalizam que não desejam prosseguir com o compartilhamento de dados. .. figure:: /_images/webhook_present_proof.png :alt: text :align: center :width: 100% Figura 4: Eventos do fluxo de pedido de prova de credencial. O diagrama abaixo representa os estados do fluxo de prova que são enviados para os webhooks, conforme o evento. .. figure:: /_images/webhook_status_present_proof.png :alt: text :align: center :width: 100% Figura 5: Máquina de estados do fluxo de pedido de prova de credencial. POST /topic/revocation-notify ============================== Este tópico trata dos eventos de revogação de credencial para agentes que utilizaram uma credencial. Uma mensagem recebida neste tópico indica a revogação de uma credencial utilizada em uma prova anterior, com um conteúdo semelhante ao exemplo abaixo: .. code-block:: json { "message": "Motivo da revogação", "proofs": [ "32a78638-f781-4ae6-929f-573a2513fcad", "b213b8e3-065a-4d5f-976a-698bb3628dbd" ] "cred_rev_uuid": "9230b99e-ab1f-4845-b1ae-e612a6dd0b7f", "cred_def_id": "BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } Observe os campos: * **message**: motivo da revogação * **proofs**: lista de identificadores de transações de prova, correspondentes aos pres_ex_ids das provas nas quais a credencial revogada foi utilizada. Esses identificadores podem ser usados para visualizar os dados da prova (ver :ref:`Visualizar registro de prova `) * **cred_rev_uuid**: identificador de revogação da credencial * **cred_def_id**: identificador da definição da credencial Emissão de credenciais ********************** Criar oferta de credencial ========================== Para emitir uma credencial, o emissor deve enviar uma oferta para o usuário utilizando uma conexão existente entre os agentes. A oferta deve usar uma definição de credencial e preencher os atributos da credencial com os dados do usuário. O webhook do portador receberá os dados e os enviará para o usuário avaliar. O usuário poderá revisar os dados e aceitar a oferta, dando seu consentimento para a emissão. Após o aceite do usuário, a credencial será emitida automaticamente e o webhook do emissor receberá os eventos do fluxo. Para criar uma oferta de credencial, use a seguinte chamada de API: :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/issue-credential-2.0/send-offer' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "auto_remove": true, "comment": "a human readable comment about the offer", "connection_id": "65796d3b-1e3b-4727-b1b8-8166cc777a90", "credential_preview": { "@type": "issue-credential/2.0/credential-preview", "attributes": [ { "name": "cred_attribute_1", "value": "value_1" }, { "name": "cred_attribute_2", "value": "value_2" } ] }, "filter": { "indy": { "cred_def_id": "BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" }, }, "trace": false }' Preencha os campos adequadamente: * **connection_id**: identificador da conexão entre agentes * **comment**: (opcional) texto descritivo da oferta * **cred_def_id**: identificador da definição da credencial (ver cadastro da carteira) * **attributes**: lista dos atributos da oferta, contendo o nome e valor de cada atributo Em caso de sucesso, a resposta será parecida com o exemplo abaixo: .. code-block:: json { "state":"offer-sent", "created_at":"2024-05-13T18:05:27.111632Z", "updated_at":"2024-05-13T18:05:27.111632Z", "cred_ex_id":"bcea07be-6d07-461c-946f-a11d3e7b9013", "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "thread_id":"5661591f-f715-4787-adf4-1ec2eeb77271", "initiator":"self", "role":"issuer", "cred_preview":{ }, "cred_proposal":{ }, "cred_offer":{ }, "by_format":{ }, "auto_offer":true, "auto_remove":false } Observe o campo **cred_ex_id** que é o identificador da transação. Ele é importante para a execução do fluxo de emissão. O webhook do portador receberá a oferta e deverá notificar o aplicativo do usuário via *push*. A oferta será apresentada ao usuário, que poderá aceitar, recusar ou gerar uma proposta. Caso o usuário aceite a oferta, a credencial é emitida automaticamente e armazenada na carteira do portador. Se o usuário recusar a oferta, o evento recebido no webhook do emissor deve ser analisado, pois pode conter a causa da recusa. Uma nova oferta pode então ser gerada, se necessário. Visualizar registro de emissão ============================== Para visualizar os dados de uma credencial é preciso consultar o registro da transação de emissão da credencial através do identificador **cred_ex_id**. Use a seguinte chamada de API: :: curl -X GET 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/issue-credential-2.0/records/$CRED_EXCH_ID' \ --header 'Authorization: Bearer $TOKEN' Em caso de sucesso, a resposta será parecida com o exemplo abaixo: .. code-block:: json { "cred_ex_record":{ "created_at":"2024-05-13T18:05:27.111632Z", "updated_at":"2024-05-13T18:05:27.111632Z", "cred_ex_id":"bcea07be-6d07-461c-946f-a11d3e7b9013", "state":"offer-sent", "cred_preview":{ "@type":"https://didcomm.org/issue-credential/2.0/credential-preview", "attributes":[ { "name":"cred_attribute_1", "value":"value_1" }, { "name":"cred_attribute_2", "value":"value_2" } ] }, "cred_proposal":{ "@type":"https://didcomm.org/issue-credential/2.0/propose-credential", "@id":"3454e43e-5c6b-440f-81f4-aafdddcff54e", "comment":"a human readable comment about the offer", "credential_preview":{ "@type":"https://didcomm.org/issue-credential/2.0/credential-preview", "attributes":[ { "name":"cred_attribute_1", "value":"value_1" }, { "name":"cred_attribute_2", "value":"value_2" } ] }, "formats":[ ], "filters~attach":[ ] }, "cred_offer":{ "@type":"https://didcomm.org/issue-credential/2.0/offer-credential", "@id":"5661591f-f715-4787-adf4-1ec2eeb77271", "~thread":{ }, "comment":"comentario", "credential_preview":{ "@type":"https://didcomm.org/issue-credential/2.0/credential-preview", "attributes":[ { "name":"cred_attribute_1", "value":"value_1" }, { "name":"cred_attribute_2", "value":"value_2" } ] }, "formats":[ ], "offers~attach":[ ] }, "by_format":{ "cred_proposal":{ "indy":{ "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } }, "cred_offer":{ "indy":{ "schema_id":"BNL8CmbhE9JSudsWcr1U8a:2:prefs:1.0", "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4", "key_correctness_proof":{ }, "nonce":"1085462880236207347265349" } } }, "auto_offer":true, "auto_remove":false "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "thread_id":"5661591f-f715-4787-adf4-1ec2eeb77271" }, "indy":{ } } O objeto da resposta contém alguns campos de interesse: * **cred_ex_record/cred_proposal/credential_preview**: objeto da proposta da credencial * **cred_ex_record/cred_offer/credential_preview**: objeto da oferta feita pelo usuário * **cred_ex_record/state**: status da emissão da credencial Criar oferta a partir de proposta de credencial =============================================== O emissor pode receber uma proposta de credencial gerada por um usuário. As propostas são recebidas no webhook do emissor, mas também é possível consultar através da chamada de API: :: curl -X GET 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/issue-credential-2.0/records?state=proposal-received' \ --header 'Authorization: Bearer $TOKEN' Observe os campos da resposta: * **cred_exch_id**: identificador da transação de emissão * **cred_proposal/credential_preview**: objeto da proposta da credencial O emissor deve avaliar os valores da proposta e gerar uma oferta com ajustes, se necessário. Para gerar a oferta para a proposta recebida, use o identificador **cred_exch_id** na chamada de API: :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/issue-credential-2.0/records/$CRED_EXCH_ID/send-offer' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "counter_preview":{ "@type":"issue-credential/2.0/credential-preview", "attributes":[ { "name":"cred_attribute_1", "value":"value_1" }, { "name":"cred_attribute_2", "value":"value_2" } ] }, "filter":{ "indy":{ "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } } }' Preencha os campos adequadamente: * **attributes**: lista dos atributos da proposta, contendo o nome e valor de cada atributo * **cred_def_id**: (opcional) identificador da definição da credencial Revogar uma credencial ====================== Uma credencial emitida para um usuário pode ser revogada pelo emissor quando, por alguma razão, ela perde a sua validade. Nem toda credencial é revogável, isso é uma característica definida na definição da credencial. A revogação pode ser realizada diretamente no portal `iD Empresas `_ (ver :ref:`Revogação de credenciais `) ou por API. Para revogar a credencial, é preciso utilizar o identificador do registro da transação de emissão **cred_ex_id** e que a transação esteja no estado **credential_acked**. Caso exista uma conexão ativa com o agente do portador, é possível indicar o **connection_id** para notificar o usuário que a credencial foi revogada. Use a seguinte chamada de API: :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/revocation/revoke' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "comment": "the reason why the credential is being revoked", "connection_id": "65796d3b-1e3b-4727-b1b8-8166cc777a90", "cred_ex_id": "bcea07be-6d07-461c-946f-a11d3e7b9013", "notify": true, "notify_version": "v1_0", "publish": true, "thread_id": "5661591f-f715-4787-adf4-1ec2eeb77271" }' Preencha adequadamente os campos: * **cred_ex_id**: identificador da transação de emissão * **notify**: (opcional) deve ser “true” para notificar o usuário que a credencial foi revogada * **connection_id**: (opcional) identificador da conexão com o agente portador. É obrigatório, se notify = true * **thread_id**: (opcional) identificador da thread da transação de emissão. É obrigatório, se notify = true Toda revogação é registrada e pode ser consultada usando a seguinte API: :: curl -X GET 'https://staging-id.cpqd.com.br/api/hub-issuer/agent/v2/revocation/credential-record' \ --header 'Authorization: Bearer $TOKEN' Prova de credenciais ******************** Criar pedido de prova ===================== Para receber os dados de uma credencial, o verificador cria um pedido de prova para o usuário, utilizando uma conexão existente entre os agentes. O verificador deve indicar quais atributos deseja receber e, opcionalmente, indicar uma definição de credencial que contenha os atributos desejados. O webhook do portador irá receber o pedido de prova e enviar para o usuário. O usuário poderá aceitar o pedido, dando seu consentimento para compartilhar seus dados. O webhook do verificador irá receber os eventos do fluxo e o resultado da prova de validação, os dados compartilhados podem ser obtidos pela API de consulta do registro da transação. Caso o usuário recuse o pedido ou ocorra um problema, o evento recebido no webhook do verificador deve ser analisado, pois pode conter a causa da recusa. Esse tipo de problema pode ocorrer quando o usuário não possui todas as credenciais solicitadas. Para criar um pedido de prova, use a seguinte chamada de API: :: curl -X POST 'https://staging-id.cpqd.com.br/api/hub-verifier/agent/present-proof-2.0/send-request' \ --header 'ApiKey: $API_KEY' \ --header 'Authorization: Bearer $TOKEN' \ --header 'Content-Type: application/json' \ --data '{ "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "comment":"A test for unbounded proof request", "presentation_request":{ "indy":{ "name":"Proof request for user documents", "non_revoked":{ "from":1714561200, "to":1714766775 }, "requested_attributes":{ "document_data":{ "names":["cpf", "rg"], "restrictions":[ { "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } ] }, "email_data":{ "name":"email", "restrictions":[ { "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } ] } }, "requested_predicates":{ }, "nonce":"1", "version":"1.0" } }, "auto_remove":false, "auto_verify":true, "trace":false }' Preencha os campos adequadamente: * **connection_id**: identificador da conexão entre agentes * **comment**: (opcional) mensagem para o usuário descrevendo o pedido de prova * **name**: nome arbitrário para a transação * **non_revoked**: (opcional) período em que a credencial deve ser válida, ou não-revogada. O período é definido em formato *epoch* e deve ser preechido para que a validação considere o critério de não-revogado * **requested_attributes**: lista de nomes arbitrários contendo a relação dos campos das credenciais que devem ser compartilhados * **cred_def_id**: (opcional) identificador da definição da credencial Em caso de sucesso, a resposta será parecida com o exemplo abaixo: .. code-block:: json { "state":"request-sent", "created_at":"2024-05-13T18:35:04.810535Z", "updated_at":"2024-05-13T18:35:04.810535Z", "trace":false, "pres_ex_id":"b64f0bf8-410d-486a-aff6-5cee86ca4e82", "connection_id":"65796d3b-1e3b-4727-b1b8-8166cc777a90", "thread_id":"c9634376-705e-4abe-a149-a39061a1a7cf", "initiator":"self", "role":"verifier", "pres_request":{ "@type":"https://didcomm.org/present-proof/2.0/request-presentation", "@id":"c9634376-705e-4abe-a149-a39061a1a7cf", "comment":"A test for unbounded proof request", "will_confirm":true, "formats":[ { "attach_id":"indy", "format":"hlindy/proof-req@v2.0" } ], "request_presentations~attach":[ { "@id":"indy", "mime-type":"application/json", "data":{ "base64":"eyJyZXF1ZXN0ZWRf...xLjAifQ==" } } ] }, "by_format":{ "pres_request":{ "indy":{ "requested_attributes":{ "document_data":{ "names":["cpf", "rg"], "restrictions":[ { "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } ] }, "email_data":{ "name":"email", "restrictions":[ { "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } ] } }, "requested_predicates":{ }, "name":"Proof request for user documents", "nonce":"1", "version":"1.0" } } }, "auto_present":false, "auto_verify":true, "auto_remove":false } O webhook do portador receberá o pedido de prova e deverá notificar o aplicativo do usuário via *push*. O pedido será apresentado ao usuário, que poderá aceitar ou recusar. Caso o usuário aceite o pedido, a credencial é compartilhada e verificada automaticamente. Se o usuário recusar, um evento de problema será enviado ao webhook do verificador. O resultado da validação e os dados compartilhados podem ser consultados através do registro da transação, conforme item a seguir. .. _devguide_proof_record: Visualizar registro de prova ============================ Para visualizar dados de uma prova de credencial é preciso consultar o registro da transação. Para isso utilize o identificador **pres_ex_id** na seguinte chamada de API: :: curl -X GET 'https://staging-id.cpqd.com.br/api/hub-verifier/agent/present-proof-2.0/records/$PRES_EXCH_ID' \ --header 'ApiKey: $API_KEY' \ --header 'Authorization: Bearer $TOKEN' .. code-block:: json { "state":"done", "created_at":"2024-05-24T18:44:06.857648Z", "updated_at":"2024-05-24T18:47:57.652950Z", "trace":false, "pres_ex_id":"91f7413e-6a79-45a5-8430-ea4e872e8aec", "connection_id":"24a785f4-5b07-450c-9956-0c08c11d7f13", "thread_id":"c086c90c-59eb-4317-ae0f-47645614edb1", "initiator":"self", "role":"verifier", "pres_request":{ }, "pres":{ }, "by_format":{ "pres_request":{ "indy":{ "version":"1.0", "requested_attributes":{ "document_data":{ "names":[ "rg", "cpf" ], "restrictions":[ { "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4" } ] } }, "requested_predicates":{ }, "name":"documentos", "nonce":"572837955284685091800653" } }, "pres":{ "indy":{ "proof":{ "proofs":[ ], "aggregated_proof":{ } }, "requested_proof":{ "revealed_attrs":{ }, "revealed_attr_groups":{ "document_data":{ "sub_proof_index":0, "values":{ "cpf":{ "raw":"value_2", "encoded":"937164683293106646596997" }, "rg":{ "raw":"value_1", "encoded":"805530634343087287422" } } } }, "self_attested_attrs":{ }, "unrevealed_attrs":{ "email_data":{ "values":{ "email":{ } } } }, "predicates":{ } }, "identifiers":[ { "schema_id":"WaSyJsvwSJVh245c9dYUwT:2:login:1.0", "cred_def_id":"BNL8CmbhE9JSudsWcr1U8a:3:CL:447:default4", "rev_reg_id":null, "timestamp":null } ] } } }, "verified":"true", "verified_msgs":[ "RMV_GLB_NRI" ], "auto_present":false, "auto_verify":true, "auto_remove":false } Observe os campos da resposta: * **by_format/pres/indy/requested_proof/revealed_attr_groups**: atributos compartilhados pelo usuário. Inspecione o campo 'values' para ler os dados do usuário * **by_format/pres/indy/requested_proof/unrevealed_attrs**: atributos solicitados e não compartilhados pelo usuário * **verified**: indica se os dados são válidos