Out-of-band metadata not persisting after invitation accepted
Hi, I'm hoping I can get some clarity here as I'm not sure if this is a bug or if I'm missing something.
I'm currently building a solution as a custodial model, and I have a single AcaPy instance running in multitenancy mode. I'm using anoncreds as the credential format.
The scenario:
- An "issuer" tenant needs to request proof from a holder tenant once an OOB invitation has been created and accepted and connections are active. I would like to use the metadata once the connection is active on the issuer tenant to create a proof request for the holder to present some credential. This is also used similarly for an issuance flow. The metadata is used to determine which attributes to request proofs for once the invite has been accepted by the holder.
The Flow:
- I create an OOB Invite via the issuer tenant as the initiating entity, and I populate the metadata attribute on the OOB Invite payload.
- Via the webhook when the invitation has been created and is in state "await_response" I can then query the connections/{conn_id}/metadata endpoint for the issuer tenant and see the metadata has persisted correctly.
- However once the invitation is accepted via the holder tenant, the connection on the issuer tenant has lost the metadata, i.e once the OOB webhook state "done" is received, the connection on the issuer tenant no longer has the metadata (connections/{conn_id}/metadata returns an empty object).
This seems odd to me. I know a proof presentation can be included as an attachment on the OOB but I have yet to add one that works so maybe that is the answer although I am struggling with that path too. The Swagger simply says indicates the structure: "attachments": [ { "id": "attachment-0", "type": "present-proof" } ] and I have yet to find clear documentation on how to achieve this attachment approach.
Any and all help would be greatly appreciated.
For a present-proof attachment, you need to first create a present-proof exchange using the /present-proof-2.0/create-request endpoint, then capture the exchange id of the response and include this as the id of the attachment. When the holder receives the OOB invitation, it will initiate the exchange. Note that you can also complete a proof verification without a connection, called a connectionless proof request.
For the metadata, you could try to have a look at the /out-of-band/invitations?oob_id=xyz endpoint, this should return you the original OOB record.
A connection record and an OOB record are 2 distinct objects.
You can also have a look at leveraging goal and goal-code to identify what issuance/presentation to create, depending on the complexity of the system you want to build.
Thank you very much for getting back to me.
I understand that OOB and Connection records are 2 distinct objects, however as I understand it, whatever metadata you include when calling out-of-band/create-invitation, it will automatically be added to the corresponding connection record (as the OOB record never keeps this metadata), which it does for the issuer who created the invitation, but only until the holder accepts the invitation, then the connection record on the issuer side loses the metadata for some reason. I think this may be a bug.
Moving on though, I am now attempting a connection-less proof but I cannot get it to work and I'm not sure what I'm doing wrong. Let me provide my flow with req/resp and the error from AcaPy for more context:
Version:
py3.12-1.3.1
AcaPy Config:
"ACAPY_MULTITENANT"="true" "ACAPY_MULTITENANT_ADMIN"="true" "ACAPY_ADMIN"="[0.0.0.0, 11000]" "ACAPY_ADMIN_INSECURE_MODE"="false" "ACAPY_LABEL"="Local" "ACAPY_AUTO_PROVISION"="true" "ACAPY_INVITE_PUBLIC"="true" "ACAPY_PUBLIC_INVITES"="true" "ACAPY_GENESIS_URL"="https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis" "ACAPY_WALLET_TYPE"="askar-anoncreds" "ACAPY_LOG_LEVEL"="debug" "ENVIRONMENT"="Development" "ACAPY_AUTO_ACCEPT_INVITES"="false" "ACAPY_AUTO_ACCEPT_REQUESTS"="false" "ACAPY_AUTO_PING_CONNECTION"="false" "ACAPY_DEBUG_CONNECTIONS"="false" "ACAPY_AUTO_RESPOND_CREDENTIAL_PROPOSAL"="false" "ACAPY_AUTO_RESPOND_CREDENTIAL_REQUEST"="false" "ACAPY_AUTO_STORE_CREDENTIAL"="true" "ACAPY_DEBUG_CREDENTIALS"="false" "ACAPY_AUTO_RESPOND_PRESENTATION_PROPOSAL"="false" "ACAPY_AUTO_RESPOND_PRESENTATION_REQUEST"="false" "ACAPY_AUTO_VERIFY_PRESENTATION"="false" "ACAPY_EMIT_NEW_DIDCOMM_PREFIX"="true" "ACAPY_EMIT_NEW_DIDCOMM_MIME_TYPE"="true" "ACAPY_EXCH_USE_UNENCRYPTED_TAGS"="false" "ACAPY_PRESERVE_EXCHANGE_RECORDS"="true" "ACAPY_READ_ONLY_LEDGER"="false" "ACAPY_MONITOR_PING"="true" "ACAPY_NOTIFY_REVOCATION"="true" "ACAPY_ANONCREDS_LEGACY_REVOCATION"="accept" "ACAPY_ADMIN_API_KEY"=acapyApiKey "ACAPY_MULTITENANT_JWT_SECRET"=acapyJwtSecret "ACAPY_WALLET_NAME"="local_base_wallet" "ACAPY_WALLET_KEY"=acapyWalletKey "ACAPY_ENDPOINT"=acapyServiceUrl "ACAPY_WALLET_SEED"="xxx" "ACAPY_AUTO_ACCEPT_TAA"="true" "ACAPY_WALLET_STORAGE_TYPE"="postgres_storage" "ACAPY_WALLET_STORAGE_CONFIG"=storageConfig "ACAPY_WALLET_STORAGE_CREDS"=storageCredentials
aca-py start --accept-taa on_file 1.3 --inbound-transport http 0.0.0.0 8000 --inbound-transport ws 0.0.0.0 8001 --inbound-transport http 0.0.0.0 8010 --outbound-transport ws --outbound-transport http"
Flow:
1) present-proof-2.0/create-request
req:
{
"auto_remove": true,
"auto_verify": false,
"comment": "",
"presentation_request": {
"anoncreds": {
"name": "Proof_Request",
"version": "1.0",
"nonce": "17538617699263846",
"requested_attributes": {
"identity_number_ERuBuDB3MjL2fzvUN2tjMy:3:CL:67689:1.0_attr": {
"name": "identity_number",
"restrictions": [
{
"cred_def_id": "ERuBuDB3MjL2fzvUN2tjMy:3:CL:67689:1.0",
"schema_id": "ERuBuDB3MjL2fzvUN2tjMy:sov:2:ProofOfPerson:1.0",
"issuer_did": "ERuBuDB3MjL2fzvUN2tjMy"
}
],
"non_revoked": {
"from": 1745999369,
"to": 1753861769
}
}
},
"requested_predicates": {}
}
},
"trace": false
}
webhook: proof topic received, state=request-sent
resp:
{
"state": "request-sent",
"created_at": "2025-07-30T07:50:10.877639Z",
"updated_at": "2025-07-30T07:50:10.877639Z",
"trace": false,
"pres_ex_id": "41ccb6a2-ed51-4423-9ad6-cd760b427c16",
"thread_id": "7a5378c1-aace-4987-ab49-0f11adb0686b",
"initiator": "self",
"role": "verifier",
"pres_request": {
"@type": "https://didcomm.org/present-proof/2.0/request-presentation",
"@id": "7a5378c1-aace-4987-ab49-0f11adb0686b",
"comment": "",
"will_confirm": true,
"formats": [
{
"attach_id": "anoncreds",
"format": "anoncreds/[email protected]"
}
],
"request_presentations~attach": [
{
"@id": "anoncreds",
"mime-type": "application/json",
"data": {
"base64": "eyJuYW1lIjogIk11bHRpX0NyZWRlbnRpYWxfUmVxdWVzdCIsICJ2ZXJzaW9uIjogIjEuMCIsICJub25jZSI6ICIxNzUzODYxNzY5OTI2Mzg0NiIsICJyZXF1ZXN0ZWRfYXR0cmlidXRlcyI6IHsiaWRlbnRpdHlfbnVtYmVyX0VSdUJ1REIzTWpMMmZ6dlVOMnRqTXk6MzpDTDo2NzY4OToxLjBfYXR0ciI6IHsibmFtZSI6ICJpZGVudGl0eV9udW1iZXIiLCAicmVzdHJpY3Rpb25zIjogW3siY3JlZF9kZWZfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTozOkNMOjY3Njg5OjEuMCIsICJzY2hlbWFfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTpzb3Y6MjpQcm9vZk9mUGVyc29uOjEuMCIsICJpc3N1ZXJfZGlkIjogIkVSdUJ1REIzTWpMMmZ6dlVOMnRqTXkifV0sICJub25fcmV2b2tlZCI6IHsiZnJvbSI6IDE3NDU5OTkzNjksICJ0byI6IDE3NTM4NjE3Njl9fX0sICJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6IHt9fQ=="
}
}
]
},
"by_format": {
"pres_request": {
"anoncreds": {
"name": "Multi_Credential_Request",
"version": "1.0",
"nonce": "17538617699263846",
"requested_attributes": {
"identity_number_ERuBuDB3MjL2fzvUN2tjMy:3:CL:67689:1.0_attr": {
"name": "identity_number",
"restrictions": [
{
"cred_def_id": "ERuBuDB3MjL2fzvUN2tjMy:3:CL:67689:1.0",
"schema_id": "ERuBuDB3MjL2fzvUN2tjMy:sov:2:ProofOfPerson:1.0",
"issuer_did": "ERuBuDB3MjL2fzvUN2tjMy"
}
],
"non_revoked": {
"from": 1745999369,
"to": 1753861769
}
}
},
"requested_predicates": {}
}
}
},
"auto_present": false,
"auto_verify": false,
"auto_remove": true
}
2) /out-of-band/create-invitation?auto_accept=true&create_unique_did=false&multi_use=false
req:
{
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"alias": "Issuer Auto Invite with Proof Request",
"attachments": [
{
"id": "41ccb6a2-ed51-4423-9ad6-cd760b427c16",
"type": "present-proof"
}
],
"goal": "Verification",
"goal_code": "proof_vc",
"handshake_protocols": [
"https://didcomm.org/didexchange/1.1"
],
"my_label": "Proof request from issuer org c9b26207-1f2a-42c4-8dd8-ca3e9a0784e2 to user a1cc03a5-80c8-493c-bcff-4b991f5174ed",
"protocol_version": "1.1",
"metadata": {
"ProofOfPerson:1.0:identity_number": "xxx"
}
}
webhook: proof topic nothing received
resp:
{
"state": "initial",
"trace": false,
"invi_msg_id": "d2e91793-7395-4b49-8354-26b9fb73a8b3",
"oob_id": "b381bee9-da3b-462e-9dbe-53c873f65f5e",
"invitation": {
"@type": "https://didcomm.org/out-of-band/1.1/invitation",
"@id": "d2e91793-7395-4b49-8354-26b9fb73a8b3",
"label": "Proof request from issuer org c9b26207-1f2a-42c4-8dd8-ca3e9a0784e2 to user a1cc03a5-80c8-493c-bcff-4b991f5174ed",
"handshake_protocols": [
"https://didcomm.org/didexchange/1.1"
],
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"requests~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": {
"@type": "https://didcomm.org/present-proof/2.0/request-presentation",
"@id": "7a5378c1-aace-4987-ab49-0f11adb0686b",
"~thread": {
"pthid": "d2e91793-7395-4b49-8354-26b9fb73a8b3"
},
"comment": "",
"will_confirm": true,
"formats": [
{
"attach_id": "anoncreds",
"format": "anoncreds/[email protected]"
}
],
"request_presentations~attach": [
{
"@id": "anoncreds",
"mime-type": "application/json",
"data": {
"base64": "eyJuYW1lIjogIk11bHRpX0NyZWRlbnRpYWxfUmVxdWVzdCIsICJ2ZXJzaW9uIjogIjEuMCIsICJub25jZSI6ICIxNzUzODYxNzY5OTI2Mzg0NiIsICJyZXF1ZXN0ZWRfYXR0cmlidXRlcyI6IHsiaWRlbnRpdHlfbnVtYmVyX0VSdUJ1REIzTWpMMmZ6dlVOMnRqTXk6MzpDTDo2NzY4OToxLjBfYXR0ciI6IHsibmFtZSI6ICJpZGVudGl0eV9udW1iZXIiLCAicmVzdHJpY3Rpb25zIjogW3siY3JlZF9kZWZfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTozOkNMOjY3Njg5OjEuMCIsICJzY2hlbWFfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTpzb3Y6MjpQcm9vZk9mUGVyc29uOjEuMCIsICJpc3N1ZXJfZGlkIjogIkVSdUJ1REIzTWpMMmZ6dlVOMnRqTXkifV0sICJub25fcmV2b2tlZCI6IHsiZnJvbSI6IDE3NDU5OTkzNjksICJ0byI6IDE3NTM4NjE3Njl9fX0sICJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6IHt9fQ=="
}
}
]
}
}
}
],
"services": [
{
"id": "#inline",
"type": "did-communication",
"recipientKeys": [
"did:key:z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9#z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9"
],
"serviceEndpoint": "http://host.docker.internal:8010"
}
],
"goal_code": "proof_vc",
"goal": "Verification"
},
"invitation_url": "http://host.docker.internal:8010?oob=eyJAdHlwZSI6ICJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzEuMS9pbnZpdGF0aW9uIiwgIkBpZCI6ICJkMmU5MTc5My03Mzk1LTRiNDktODM1NC0yNmI5ZmI3M2E4YjMiLCAibGFiZWwiOiAiUHJvb2YgcmVxdWVzdCBmcm9tIGlzc3VlciBvcmcgYzliMjYyMDctMWYyYS00MmM0LThkZDgtY2EzZTlhMDc4NGUyIHRvIHVzZXIgYTFjYzAzYTUtODBjOC00OTNjLWJjZmYtNGI5OTFmNTE3NGVkIiwgImhhbmRzaGFrZV9wcm90b2NvbHMiOiBbImh0dHBzOi8vZGlkY29tbS5vcmcvZGlkZXhjaGFuZ2UvMS4xIl0sICJhY2NlcHQiOiBbImRpZGNvbW0vYWlwMSIsICJkaWRjb21tL2FpcDI7ZW52PXJmYzE5Il0sICJyZXF1ZXN0c35hdHRhY2giOiBbeyJAaWQiOiAicmVxdWVzdC0wIiwgIm1pbWUtdHlwZSI6ICJhcHBsaWNhdGlvbi9qc29uIiwgImRhdGEiOiB7Impzb24iOiB7IkB0eXBlIjogImh0dHBzOi8vZGlkY29tbS5vcmcvcHJlc2VudC1wcm9vZi8yLjAvcmVxdWVzdC1wcmVzZW50YXRpb24iLCAiQGlkIjogIjdhNTM3OGMxLWFhY2UtNDk4Ny1hYjQ5LTBmMTFhZGIwNjg2YiIsICJ-dGhyZWFkIjogeyJwdGhpZCI6ICJkMmU5MTc5My03Mzk1LTRiNDktODM1NC0yNmI5ZmI3M2E4YjMifSwgImNvbW1lbnQiOiAiIiwgIndpbGxfY29uZmlybSI6IHRydWUsICJmb3JtYXRzIjogW3siYXR0YWNoX2lkIjogImFub25jcmVkcyIsICJmb3JtYXQiOiAiYW5vbmNyZWRzL3Byb29mLXJlcXVlc3RAdjEuMCJ9XSwgInJlcXVlc3RfcHJlc2VudGF0aW9uc35hdHRhY2giOiBbeyJAaWQiOiAiYW5vbmNyZWRzIiwgIm1pbWUtdHlwZSI6ICJhcHBsaWNhdGlvbi9qc29uIiwgImRhdGEiOiB7ImJhc2U2NCI6ICJleUp1WVcxbElqb2dJazExYkhScFgwTnlaV1JsYm5ScFlXeGZVbVZ4ZFdWemRDSXNJQ0oyWlhKemFXOXVJam9nSWpFdU1DSXNJQ0p1YjI1alpTSTZJQ0l4TnpVek9EWXhOelk1T1RJMk16ZzBOaUlzSUNKeVpYRjFaWE4wWldSZllYUjBjbWxpZFhSbGN5STZJSHNpYVdSbGJuUnBkSGxmYm5WdFltVnlYMFZTZFVKMVJFSXpUV3BNTW1aNmRsVk9NblJxVFhrNk16cERURG8yTnpZNE9Ub3hMakJmWVhSMGNpSTZJSHNpYm1GdFpTSTZJQ0pwWkdWdWRHbDBlVjl1ZFcxaVpYSWlMQ0FpY21WemRISnBZM1JwYjI1eklqb2dXM3NpWTNKbFpGOWtaV1pmYVdRaU9pQWlSVkoxUW5WRVFqTk5ha3d5Wm5wMlZVNHlkR3BOZVRvek9rTk1PalkzTmpnNU9qRXVNQ0lzSUNKelkyaGxiV0ZmYVdRaU9pQWlSVkoxUW5WRVFqTk5ha3d5Wm5wMlZVNHlkR3BOZVRwemIzWTZNanBRY205dlprOW1VR1Z5YzI5dU9qRXVNQ0lzSUNKcGMzTjFaWEpmWkdsa0lqb2dJa1ZTZFVKMVJFSXpUV3BNTW1aNmRsVk9NblJxVFhraWZWMHNJQ0p1YjI1ZmNtVjJiMnRsWkNJNklIc2labkp2YlNJNklERTNORFU1T1Rrek5qa3NJQ0owYnlJNklERTNOVE00TmpFM05qbDlmWDBzSUNKeVpYRjFaWE4wWldSZmNISmxaR2xqWVhSbGN5STZJSHQ5ZlE9PSJ9fV19fX1dLCAic2VydmljZXMiOiBbeyJpZCI6ICIjaW5saW5lIiwgInR5cGUiOiAiZGlkLWNvbW11bmljYXRpb24iLCAicmVjaXBpZW50S2V5cyI6IFsiZGlkOmtleTp6Nk1rakdOcnZac2tzdTJkM1ZWcFdDYnVHb01Fb1o3blpGakhTckJDcnVkVkd1VTkjejZNa2pHTnJ2WnNrc3UyZDNWVnBXQ2J1R29NRW9aN25aRmpIU3JCQ3J1ZFZHdVU5Il0sICJzZXJ2aWNlRW5kcG9pbnQiOiAiaHR0cDovL2hvc3QuZG9ja2VyLmludGVybmFsOjgwMTAifV0sICJnb2FsX2NvZGUiOiAicHJvb2ZfdmMiLCAiZ29hbCI6ICJWZXJpZmljYXRpb24ifQ"
}
3) /out-of-band/receive-invitation?alias=Holder%20Accept%20Auto%20Invite%20with%20Proof%20Request&auto_accept=true&use_existing_connection=false
req:
{
"@type": "https://didcomm.org/out-of-band/1.1/invitation",
"@id": "d2e91793-7395-4b49-8354-26b9fb73a8b3",
"label": "Proof request from issuer org c9b26207-1f2a-42c4-8dd8-ca3e9a0784e2 to user a1cc03a5-80c8-493c-bcff-4b991f5174ed",
"handshake_protocols": [
"https://didcomm.org/didexchange/1.1"
],
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"requests~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": {
"@type": "https://didcomm.org/present-proof/2.0/request-presentation",
"@id": "7a5378c1-aace-4987-ab49-0f11adb0686b",
"~thread": {
"pthid": "d2e91793-7395-4b49-8354-26b9fb73a8b3"
},
"comment": "",
"will_confirm": true,
"formats": [
{
"attach_id": "anoncreds",
"format": "anoncreds/[email protected]"
}
],
"request_presentations~attach": [
{
"@id": "anoncreds",
"mime-type": "application/json",
"data": {
"base64": "eyJuYW1lIjogIk11bHRpX0NyZWRlbnRpYWxfUmVxdWVzdCIsICJ2ZXJzaW9uIjogIjEuMCIsICJub25jZSI6ICIxNzUzODYxNzY5OTI2Mzg0NiIsICJyZXF1ZXN0ZWRfYXR0cmlidXRlcyI6IHsiaWRlbnRpdHlfbnVtYmVyX0VSdUJ1REIzTWpMMmZ6dlVOMnRqTXk6MzpDTDo2NzY4OToxLjBfYXR0ciI6IHsibmFtZSI6ICJpZGVudGl0eV9udW1iZXIiLCAicmVzdHJpY3Rpb25zIjogW3siY3JlZF9kZWZfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTozOkNMOjY3Njg5OjEuMCIsICJzY2hlbWFfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTpzb3Y6MjpQcm9vZk9mUGVyc29uOjEuMCIsICJpc3N1ZXJfZGlkIjogIkVSdUJ1REIzTWpMMmZ6dlVOMnRqTXkifV0sICJub25fcmV2b2tlZCI6IHsiZnJvbSI6IDE3NDU5OTkzNjksICJ0byI6IDE3NTM4NjE3Njl9fX0sICJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6IHt9fQ=="
}
}
]
}
}
}
],
"services": [
{
"id": "#inline",
"type": "did-communication",
"recipientKeys": [
"did:key:z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9#z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9"
],
"serviceEndpoint": "http://host.docker.internal:8010"
}
],
"goal_code": "proof_vc",
"goal": "Verification"
}
webhook: proof topic nothing received
resp:
{
"state": "deleted",
"created_at": "2025-07-30T07:57:45.547651Z",
"updated_at": "2025-07-30T07:57:45.547651Z",
"trace": false,
"oob_id": "c6baae30-9368-47c1-9f80-a6ca527b1b4a",
"invi_msg_id": "d2e91793-7395-4b49-8354-26b9fb73a8b3",
"invitation": {
"@type": "https://didcomm.org/out-of-band/1.1/invitation",
"@id": "d2e91793-7395-4b49-8354-26b9fb73a8b3",
"label": "Proof request from issuer org c9b26207-1f2a-42c4-8dd8-ca3e9a0784e2 to user a1cc03a5-80c8-493c-bcff-4b991f5174ed",
"handshake_protocols": [
"https://didcomm.org/didexchange/1.1"
],
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"requests~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": {
"@type": "https://didcomm.org/present-proof/2.0/request-presentation",
"@id": "7a5378c1-aace-4987-ab49-0f11adb0686b",
"~thread": {
"pthid": "d2e91793-7395-4b49-8354-26b9fb73a8b3"
},
"comment": "",
"will_confirm": true,
"formats": [
{
"attach_id": "anoncreds",
"format": "anoncreds/[email protected]"
}
],
"request_presentations~attach": [
{
"@id": "anoncreds",
"mime-type": "application/json",
"data": {
"base64": "eyJuYW1lIjogIk11bHRpX0NyZWRlbnRpYWxfUmVxdWVzdCIsICJ2ZXJzaW9uIjogIjEuMCIsICJub25jZSI6ICIxNzUzODYxNzY5OTI2Mzg0NiIsICJyZXF1ZXN0ZWRfYXR0cmlidXRlcyI6IHsiaWRlbnRpdHlfbnVtYmVyX0VSdUJ1REIzTWpMMmZ6dlVOMnRqTXk6MzpDTDo2NzY4OToxLjBfYXR0ciI6IHsibmFtZSI6ICJpZGVudGl0eV9udW1iZXIiLCAicmVzdHJpY3Rpb25zIjogW3siY3JlZF9kZWZfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTozOkNMOjY3Njg5OjEuMCIsICJzY2hlbWFfaWQiOiAiRVJ1QnVEQjNNakwyZnp2VU4ydGpNeTpzb3Y6MjpQcm9vZk9mUGVyc29uOjEuMCIsICJpc3N1ZXJfZGlkIjogIkVSdUJ1REIzTWpMMmZ6dlVOMnRqTXkifV0sICJub25fcmV2b2tlZCI6IHsiZnJvbSI6IDE3NDU5OTkzNjksICJ0byI6IDE3NTM4NjE3Njl9fX0sICJyZXF1ZXN0ZWRfcHJlZGljYXRlcyI6IHt9fQ=="
}
}
]
}
}
}
],
"services": [
{
"id": "#inline",
"type": "did-communication",
"recipientKeys": [
"did:key:z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9#z6MkjGNrvZsksu2d3VVpWCbuGoMEoZ7nZFjHSrBCrudVGuU9"
],
"serviceEndpoint": "http://host.docker.internal:8010"
}
],
"goal_code": "proof_vc",
"goal": "Verification"
},
"connection_id": "54486657-2efb-4a82-b8e0-e2690729e791",
"role": "receiver",
"multi_use": false
}
Acapy error for holder:
2025-07-30 07:57:45,662 acapy_agent.core.conductor ERROR Storage error occurred in message handler: StorageNotFoundError: Record not found: connection/54486657-2efb-4a82-b8e0-e2690729e791
Traceback (most recent call last):
File "/usr/local/lib/python3.12/asyncio/tasks.py", line 314, in __step_run_and_handle_result
result = coro.send(None)
^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/core/dispatcher.py", line 229, in handle_v1_message
connection = await ConnRecord.retrieve_by_id(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/messaging/models/base_record.py", line 235, in retrieve_by_id
result = await storage.get_record(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/storage/askar.py", line 91, in get_record
raise StorageNotFoundError(f"Record not found: {record_type}/{record_id}")
acapy_agent.storage.error.StorageNotFoundError: Record not found: connection/54486657-2efb-4a82-b8e0-e2690729e791
2025-07-30 07:57:45,662 acapy_agent.core.dispatcher ERROR Handler error: Dispatcher.handle_v1_message
Traceback (most recent call last):
File "/usr/local/lib/python3.12/asyncio/tasks.py", line 314, in __step_run_and_handle_result
result = coro.send(None)
^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/core/dispatcher.py", line 229, in handle_v1_message
connection = await ConnRecord.retrieve_by_id(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/messaging/models/base_record.py", line 235, in retrieve_by_id
result = await storage.get_record(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/storage/askar.py", line 91, in get_record
raise StorageNotFoundError(f"Record not found: {record_type}/{record_id}")
acapy_agent.storage.error.StorageNotFoundError: Record not found: connection/54486657-2efb-4a82-b8e0-e2690729e791
Thanks in advance!
I did some digging around, here what is happening:
When the OOB invitation is created with a handshake, it creates a connection and metadata records: https://github.com/openwallet-foundation/acapy/blob/ce8dac422b5ffbf6c5925afc2068e0eeb8c3753a/acapy_agent/protocols/out_of_band/v1_0/manager.py#L322
When the holder receives the recipient's did exchange request, it will process it and then call the clean_finished_oob_record function:
https://github.com/openwallet-foundation/acapy/blob/ce8dac422b5ffbf6c5925afc2068e0eeb8c3753a/acapy_agent/protocols/didexchange/v1_0/manager.py#L561
That function will then delete the oob record if its not multiuse and contains not attachment: https://github.com/openwallet-foundation/acapy/blob/ce8dac422b5ffbf6c5925afc2068e0eeb8c3753a/acapy_agent/core/oob_processor.py#L66
This in turn deletes the metadata: https://github.com/openwallet-foundation/acapy/blob/ce8dac422b5ffbf6c5925afc2068e0eeb8c3753a/acapy_agent/protocols/out_of_band/v1_0/models/oob_record.py#L152
I agree with you that it should be persisted, this should probably be raised.
For the connectionless, you can try creating the OOB invitation without the handshake parameter. If I remember correctly this is how I did it in the past.
Is your holder / issuer on the same multitenant agent or are they 2 completely separate acapy instances?
https://github.com/openwallet-foundation/acapy-vc-authn-oidc does connection-less verification, if that is of any help. I think it recently added connections as well.
I did some digging around, here what is happening:
When the OOB invitation is created with a handshake, it creates a connection and metadata records:
acapy/acapy_agent/protocols/out_of_band/v1_0/manager.py
Line 322 in ce8dac4
await conn_rec.metadata_set(session, key, value) When the holder receives the recipient's did exchange request, it will process it and then call the
clean_finished_oob_recordfunction:acapy/acapy_agent/protocols/didexchange/v1_0/manager.py
Line 561 in ce8dac4
await oob_processor.clean_finished_oob_record(self.profile, request) That function will then delete the oob record if its not multiuse and contains not attachment:
acapy/acapy_agent/core/oob_processor.py
Line 66 in ce8dac4
await oob_record.delete_record(session) This in turn deletes the metadata:
acapy/acapy_agent/protocols/out_of_band/v1_0/models/oob_record.py
Line 152 in ce8dac4
Delete metadata
I agree with you that it should be persisted, this should probably be raised.
Thank you so much! I thought I was going mad :)
For the connectionless, you can try creating the OOB invitation without the
handshakeparameter. If I remember correctly this is how I did it in the past.Is your holder / issuer on the same multitenant agent or are they 2 completely separate acapy instances?
Currently it's a single agent so issuer/verifier/holder all live in the one instance. Having said that I do need to check if that's the way we will go for Prod. I'm fairly new to SSI and acapy so my understanding is that I should be able to spin up separate instances and just point my backend(s) to the separate acapy instances and set up my relevant webhooks correctly depending on use case (if I need to issue v.s perform custodial actions on user wallets etc). Is there anything I should be taking note of here?
https://github.com/openwallet-foundation/acapy-vc-authn-oidc does connection-less verification, if that is of any help. I think it recently added connections as well.
Thanks for this, we have OIDC capability on our roadmap but at the moment our solution is built on just calling the acapy agent admin endpoints. Having said that it could be most useful if we integrate OIDC4VCI & VP into our IDP (still in alpha to support those standards) in future
The reason I was asking how your agents are setup is that if its all in the same agent, it can be difficult to know who is raising the error in the logs (in the case of your connection-less presentation request scenario). Since your flags are set to automatic, when you receive an invitation from the holder tenant, it will trigger a series of events through the didcomm exchange and it could be the "issuer" tenant that raises the error. Isolating both agents could help narrow this down during experimentation / development.
You deployment makes sense, but can make debugging more difficult when reading the logs and following who does what.
A contribution would be welcome to address the metadata issue you raised.
The reason I was asking how your agents are setup is that if its all in the same agent, it can be difficult to know who is raising the error in the logs (in the case of your connection-less presentation request scenario). Since your flags are set to automatic, when you receive an invitation from the holder tenant, it will trigger a series of events through the didcomm exchange and it could be the "issuer" tenant that raises the error. Isolating both agents could help narrow this down during experimentation / development.
You deployment makes sense, but can make debugging more difficult when reading the logs and following who does what.
Thank you for the pointers, will have a look at splitting them accordingly.
contribution would be welcome to address the metadata issue you raised.
Cool, I'll try PR something soonest