Out-of-band credential offer invitation not recognized correctly by the receiving sub-wallet (multi-tenant ACA-Py)
Summary
When Agent A creates an issue-credential-v2 offer and embeds it inside an Out-of-Band (OOB) invitation (connectionless), Agent B receives the OOB URL but ACA-Py does not correctly identify it as a credential offer.
The formats, or requests~attach information is not parsed correctly on the receiving agent, causing incorrect request-type detection.
Environment
- ACA-Py version: 1.3.0
- Multi-tenant mode: Enabled
- Wallets involved: Sub-wallet A → sub-wallet B (Same multi-tenant Agent, different 2 wallets)
- Message protocol: AIP 2.0 (Issue-Credential-v2)
- DID method used: did:peer:4
- Transport: HTTP
Steps to Reproduce
1. Agent A creates an Issue-Credential-V2 offer
POST /issue-credential-2.0/create-offer
Payload sent:
{
"comment": "comment",
"credential_preview": {
"attributes": [
{
"name": "first_name",
"value": "John"
},
{
"name": "last_name",
"value": "Doe"
},
{
"name": "dob_dateint",
"value": "1990-01-01"
}
]
},
"filter": {
"indy": {
"cred_def_id": "2w1Eux9fobtPbXAbPJpjip:3:CL:3012385:Id_card",
"issuer_did": "2w1Eux9fobtPbXAbPJpjip",
"schema_issuer_did": "2w1Eux9fobtPbXAbPJpjip",
"schema_name": "Id_card",
"schema_version": "1.0"
}
},
"auto_issue": true
}
2. Agent A wraps the offer into an OOB invitation
POST /out-of-band/create-invitation
Payload sent:
{
"accept": [
"AIP2_0"
],
"handshake_protocols": [
"https://didcomm.org/didexchange/1.0"
],
"attachments": [
{
"id": "65f56f23-083a-4ba6-83e6-40dba01dd993",
"type": "credential-offer"
}
],
"goal_code": "issue-vc",
"goal": "issue a credential",
"use_did_method": "did:peer:4"
}
This produces the OOB URL.
3. Agent B receives the OOB invitation URL
Receiver extracts:
{
"@type": "https://didcomm.org/out-of-band/1.1/invitation",
"@id": "34d31174-e7d2-40f3-86c7-2c85d03d4ce7",
"label": "tenant agent",
"handshake_protocols": [
"https://didcomm.org/didexchange/1.0"
],
"accept": [
"AIP2_0"
],
"requests~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": {
"@type": "https://didcomm.org/issue-credential/2.0/offer-credential",
"@id": "8c426c96-7e6c-4c7c-9839-5392d21e293a",
"~thread": {
"pthid": "34d31174-e7d2-40f3-86c7-2c85d03d4ce7"
},
"comment": "comment",
"credential_preview": {
"@type": "https://didcomm.org/issue-credential/2.0/credential-preview",
"attributes": [
{
"name": "first_name",
"value": "John"
},
{
"name": "last_name",
"value": "Doe"
},
{
"name": "dob_dateint",
"value": "1990-01-01"
}
]
},
"formats": [
{
"attach_id": "indy",
"format": "hlindy/[email protected]"
}
],
"offers~attach": [
{
"@id": "indy",
"mime-type": "application/json",
"data": {
"base64": " ... Base64 ..."
}
}
]
}
}
}
],
"services": [
"did:peer:4zQmZ8MpHxdbJWvPhuqz4bsVDLJQeRNo7SFECGgiVggQ2tVj:zUU97Vo6H3YqMGZRx6Z7MD3fmnzFX1wUCBfnoGBouTWLBzKL6HrhSMi7mNNiyuAJuhRaPCizPjpyuL7kycFSckZWVWu4bW2kQpk3apMmvU9JYXoDNytacTHTXtJUzegEv5Rh9PmFmPwfPgzv8igx3yhyj3DkVEf974Nawgi1HAtXhZytdzpdhcikq6syS8ieGUE6d7n7zaUMSS1fh5CejBrpYCqGMyzu8qEjkiP6SKyWUG7ZtHrLYffxgaw5kQrjRQcMy114xo5SbKXb3KPKkEUcU2zZfrduqeNtkDFd6TyhERtHdHqvSCWPLHRFpD4gTbzbUqzm31AqajRipnNP8coQFrPMQ3SCtTMJs99pjjLAYWA5qBEduiNpMQjnF3fef7cKKeS9m7gBZoFdAsYVe6chmBaYJ5w5RsCGrGPKcMzQt1VZDc9C2zfFAQeLEikWGMGjDegGaAmfhvvomun39qiyEMxeNHoy8UMiH3xDMKjrHoiQWteZE2AwnegJEJWje8NZPFV7wmdNZJr4SWpJG2b9ZM6DVJfHrKsS93dvvdV8kYy1WeAr2Zb8YEZjaTqQabNYv9dyT1s3h3pFjLL69dqQ3v4uwi"
]
}
4. Agent B calls
POST /out-of-band/receive-invitation
Expected Result
Agent B should classify this as:
- Credential Offer
- Goal code → issue-vc
- formats → hlindy/[email protected]
- Type → Issue-Credential-V2 flow
Actual Result
- Receiver cannot determine the request type reliably.
formatsnot consistently passed into the finalOobRecord.- ACA-Py returns incomplete
OobRecordfor invitations that include credential offers.
Actual Logs
Output from:
POST /out-of-band/receive-invitation
Traceback (most recent call last):
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 444, in resolve_didcomm_services
doc_dict: dict = await resolver.resolve(self._profile, did, service_accept)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 87, in resolve
_, doc = await self._resolve(profile, did, service_accept, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 76, in _resolve
raise DIDNotFound(f"DID {did} could not be resolved")
acapy_agent.resolver.base.DIDNotFound: DID did:peer:4zQmZ8MpHxdbJWvPhuqz4bsVDLJQeRNo7SFECGgiVggQ2tVj could not be resolved
The above exception was the direct cause of the following exception:
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/admin/server.py", line 222, in upgrade_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp_apispec/middlewares.py", line 51, in validation_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/decorators/auth.py", line 84, in tenant_auth
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/routes.py", line 372, in invitation_receive
result = await oob_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 755, in receive_invitation
oob_record = await self._perform_handshake(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 1091, in _perform_handshake
conn_record = await didx_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/didexchange/v1_0/manager.py", line 159, in receive_invitation
targets = await self.resolve_connection_targets(conn_rec.their_public_did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 541, in resolve_connection_targets
doc, didcomm_services = await self.resolve_didcomm_services(did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 447, in resolve_didcomm_services
raise BaseConnectionManagerError("Failed to resolve DID services") from error
acapy_agent.connections.base_manager.BaseConnectionManagerError: Failed to resolve DID services
2025-12-01 11:50:30,806 acapy_agent.admin.server ERROR Handler error with exception:
Traceback (most recent call last):
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 444, in resolve_didcomm_services
doc_dict: dict = await resolver.resolve(self._profile, did, service_accept)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 87, in resolve
_, doc = await self._resolve(profile, did, service_accept, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 76, in _resolve
raise DIDNotFound(f"DID {did} could not be resolved")
acapy_agent.resolver.base.DIDNotFound: DID did:peer:4zQmZ8MpHxdbJWvPhuqz4bsVDLJQeRNo7SFECGgiVggQ2tVj could not be resolved
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 146, in ready_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 238, in debug_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 360, in setup_context
return await task
^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/futures.py", line 289, in __await__
yield self # This tells Task to wait for completion.
^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
future.result()
File "/usr/local/lib/python3.12/asyncio/futures.py", line 202, in result
raise self._exception.with_traceback(self._exception_tb)
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/admin/server.py", line 222, in upgrade_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp_apispec/middlewares.py", line 51, in validation_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/decorators/auth.py", line 84, in tenant_auth
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/routes.py", line 372, in invitation_receive
result = await oob_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 755, in receive_invitation
oob_record = await self._perform_handshake(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 1091, in _perform_handshake
conn_record = await didx_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/didexchange/v1_0/manager.py", line 159, in receive_invitation
targets = await self.resolve_connection_targets(conn_rec.their_public_did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 541, in resolve_connection_targets
doc, didcomm_services = await self.resolve_didcomm_services(did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 447, in resolve_didcomm_services
raise BaseConnectionManagerError("Failed to resolve DID services") from error
acapy_agent.connections.base_manager.BaseConnectionManagerError: Failed to resolve DID services
2025-12-01 11:50:30,808 aiohttp.server ERROR Error handling request from 192.168.1.13
Traceback (most recent call last):
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 444, in resolve_didcomm_services
doc_dict: dict = await resolver.resolve(self._profile, did, service_accept)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 87, in resolve
_, doc = await self._resolve(profile, did, service_accept, timeout=timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/resolver/did_resolver.py", line 76, in _resolve
raise DIDNotFound(f"DID {did} could not be resolved")
acapy_agent.resolver.base.DIDNotFound: DID did:peer:4zQmZ8MpHxdbJWvPhuqz4bsVDLJQeRNo7SFECGgiVggQ2tVj could not be resolved
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp/web_protocol.py", line 510, in _handle_request
resp = await request_handler(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp/web_app.py", line 569, in _handle
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp/web_middlewares.py", line 117, in impl
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 146, in ready_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 238, in debug_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/server.py", line 360, in setup_context
return await task
^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/futures.py", line 289, in __await__
yield self # This tells Task to wait for completion.
^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/tasks.py", line 385, in __wakeup
future.result()
File "/usr/local/lib/python3.12/asyncio/futures.py", line 202, in result
raise self._exception.with_traceback(self._exception_tb)
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/admin/server.py", line 222, in upgrade_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/aiohttp_apispec/middlewares.py", line 51, in validation_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/admin/decorators/auth.py", line 84, in tenant_auth
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/routes.py", line 372, in invitation_receive
result = await oob_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 755, in receive_invitation
oob_record = await self._perform_handshake(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/out_of_band/v1_0/manager.py", line 1091, in _perform_handshake
conn_record = await didx_mgr.receive_invitation(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/protocols/didexchange/v1_0/manager.py", line 159, in receive_invitation
targets = await self.resolve_connection_targets(conn_rec.their_public_did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 541, in resolve_connection_targets
doc, didcomm_services = await self.resolve_didcomm_services(did)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/aries/.local/lib/python3.12/site-packages/acapy_agent/connections/base_manager.py", line 447, in resolve_didcomm_services
raise BaseConnectionManagerError("Failed to resolve DID services") from error
acapy_agent.connections.base_manager.BaseConnectionManagerError: Failed to resolve DID services
Problem Summary
The OOB invitation contains a valid Issue-Credential-V2 offer, but ACA-Py’s internal handling of OOB 1.1 → credential offer mapping appears inconsistent:
formatsinsiderequests~attachare not reflected in the returned OOB record.- Makes it impossible to reliably detect credential-offer vs proof-request on the receiving agent.
This behavior breaks multi-tenant workflows where OOB invitations are used to pass issue-credential-v2 offers.
Possible Causes
requests~attach→credJsonnot parsed fully- Issue-Credential-V2 “offer-credential” is not mapped to ACA-Py goal codes internally
Request
Please confirm whether:
- This is an intended ACA-Py behavior OR
- A bug in OOB v1.1 behavior when wrapping Issue-Credential-v2 offer messages.
If this is a bug, guidance or a fix would be appreciated.
Additional Context
This use-case is sub-wallet to sub-wallet (same agent instance) .
This seems like a bug in OOB v1.1 behavior.
For some reason it isn't able to resolve the did:peer:4. Could you try using https://didcomm.org/didexchange/1.1 handshake_protocol and see if the same error happens? It uses did:peer:4 by default.
We will probably need to debug the protocol on the holder side to determine why that did isn't resolvable. I don't have any answers for you. It would likely work if you create the connection first and then issue the credential instead of adding as an attachment.
There has been some other issues opened up about the invitation attachment which haven't been fixed. This could also just be a acapy holder issue and the invitation may work correctly when consumed by a credo-ts agent.