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.

text

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 Primeiros Passos) e possuir um token de acesso.

Ambientes

Staging

O ambiente de staging possui os seguintes endereços:

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).

Hub do Emissor

AFA

https://staging-id.cpqd.com.br/api/hub-issuer/afa

Agente

https://staging-id.cpqd.com.br/api/hub-issuer/agent

Hub do Verificador

AFA

https://staging-id.cpqd.com.br/api/hub-verifier/afa

Agente

https://staging-id.cpqd.com.br/api/hub-verifier/agent

Hub do Portador

AFA

https://staging-id.cpqd.com.br/api/hub-holder/afa

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:

{
  "token": "<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).

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:

{
  "token": "<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:

{
    "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:

{
    "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:

{"@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:

{
    "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.

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 Configuração de Webhooks).

O agente enviará requisições HTTP REST para os webhooks cadastrados, adicionando o caminho /topic/<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:

{
    "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:

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.

text

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:

{
    "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:

{
    "@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:

{
    "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:

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.

text

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.

text

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.

{
    "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:

{
    "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:

{
    "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:

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.

text

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.

text

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:

{
    "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 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:

{
    "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:

{
    "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 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:

{
    "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.

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'
{
    "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