Create web invitation-based custodial B2B wallets
Overview
This guide will teach you how to set up invitation-based custodial wallets for business-to-business (B2B) scenarios on a web platform provided by a wallet provider. In a B2B context, multiple users from the same organization may need access to a shared wallet with role-based permissions. For simplicity, we will refer to this as a “wallet,” though in B2B it often functions more like credential storage.
Business Context: While the term "wallet" is intuitive in retail contexts where individuals manage their own assets, B2B environments involve shared ownership and access. For businesses, this "wallet" is more of a credential repository with varying access levels for different departments or roles. This solution supports secure collaboration and compliance within organizations.
Step 1: Register and set up a custodial wallet for an organization
Goal:
Register an organization or department using a specific email address and automatically create a tenant, credential storage, and DID under the hood.
Business context:
Why invitation-based custodial wallets? Requiring each organization or department to manually register with a dedicated email (for example, compliance@bank-abc.com
or back-office@bank-abc.com
) simplifies the onboarding process. It ensures that the wallet provider handles the creation of tenants and the generation of DIDs, eliminating the need for complex manual setup. This design supports operational scalability while maintaining control over wallet creation.
Instructions
Manual registration via Signup page
Use the Truvity signup page to register each organization or department and confirm their email addresses. For example:
- Register the Compliance Department with the email
compliance@bank-abc.com
- Register the Back Office Department with the email
back-office@bank-abc.com
Automatic creation of tenant and credential storage
After successful signup, the platform automatically creates the following under the hood:
- A tenant (representing the department).
- A credential storage associated with that tenant.
- A Decentralized Identifier (DID) assigned to the tenant.
Assigning a unique DID to each organization or department provides a verifiable digital identity, which ensures that any credentials issued or shared are traceable back to a legitimate entity. This structure enhances trust in credential exchanges.
Step 2: Organize storage data using labels
Goal:
Structure wallet data with labels to control access and provide fine-grained resource management. In the context of this guide, we will use labels to separate access to resources within one department based on the roles accessing these resources.
As mentioned at the beginning of this guide, we assume that each department has its own tenant and tenant users are not able to access resources of another tenant, therefore we will not use labels to separate access to resources based on the department.
Business context:
Why use labels? In a large organization, different roles even within a single department (for example, Compliance Analyst, Anti-Money Laundering (AML) Officer, KYC (Know Your Customer) Specialist) require access to different sets of credentials. Labels provide a flexible way to manually or automatically categorize and manage credentials and other resources based on various attributes, such as role or set of customers. This simplifies managing complex data and ensures that the right credentials are accessible to the right people.
Instructions
Attach labels to credentials
When issuing or receiving credentials, you can attach appropriate labels to the resources to use them later on for access control.
Let's take the following example: only AML officers need to be able to see certain DIDComm messages that were sent to other parties. To achieve this, we will attach the role
label with the aml-officer
value to the DIDComm message when sending it:
- TypeScript
- Java
await client.didcommMessages.didCommMessageSend({
data: {
to: 'recipientDid',
keyId: 'key_id'
},
labels: {
role: 'aml-officer'
}
});
client.didcommMessages()
.didCommMessageSend(DidCommMessageSendInput.builder()
.data(DidCommMessageSend.builder()
.to("recipientDid")
.keyId("key_ud")
.build())
.labels(Map.of("role", "aml-officer"))
.build());
If you want to learn more about sharing credentials and presentations using DIDComm messaging, refer to the following page.
Labels act as a form of metadata that allows for granular control over who can view or act upon resources. This approach supports organizational policies around data segregation and compliance, making it easier to audit access and ensure that only authorized individuals handle sensitive data.
Step 3: Implement faceted search for resource access
Goal:
Enable users to search for and access labeled resources based on their role using faceted search.
Business context:
Why faceted search? In B2B scenarios, where the organization may store numerous resources, it is essential for users to quickly locate the resources they only have access to and the credentials they need. Faceted search allows users to filter resources based on various criteria (e.g., role), improving security and efficiency in credential management.
Instructions
Use faceted search to search for resources
You can use faceted search to query the resource database based on labels. Let's expand on the example from the previous step and search for all DIDComm messages marked with the role
label and the aml-officer
label value:
- TypeScript
- Java
const searchResult = await client.didcommMessages.didcommMessageSearch({
filter: [
{
labels: [
{
operator: 'EQUAL',
key: 'role',
value: 'aml-officer',
},
],
},
],
});
ListDidCommMessage searchResult = client.didcommMessages()
.didcommMessageSearch(DidcommMessageSearchRequest.builder()
.filter(List.of(DidcommMessageFilter.builder()
.labels(List.of(
DidcommMessageFilterLabelsItem.equal(KeyEqualLabelKeyLabelValue.builder()
.key("role")
.value("aml-officer")
.build())))
.build()))
.build());
In the above example, faceted search serves two needs:
- It improves security by separating access to resources based on labels.
- It improves operational efficiency by allowing users to filter large datasets of resources to quickly find what they need. This is especially useful in large organizations with multiple roles, where the number or credentials and other resources might be big.
If you want to learn more about using faceted search, refer to the following page.
Step 4: Share credentials using DIDComm Messaging
Goal:
Enable secure credential sharing between wallets while storing a verifiable record of the transaction (message) between two parties.
Business context:
Why use DIDComm for credential sharing? In B2B scenarios, logging transactions is critical for regulatory compliance, dispute resolution, and ensuring accountability between parties. DIDComm messaging not only encrypts the credential exchange but also ensures that the transaction itself is logged, which can be used for auditing purposes.
Instructions
Send credentials via DIDComm
Create a DIDComm message to send one or more credentials (as a verifiable presentation) to another wallet.
If you want to learn more about issuing credentials, refer to the following page.
In the exmple below, the DIDComm message contains one verifiable credential.
- TypeScript
- Java
await credential.send('recipientDid', key.id);
credential.send("recipientDid", privateKey.getId());
If you want to learn more about sending verifiable credentials and presentations using DIDComm Messaging, refer to the following page.
Get notified of received DIDComm messages
Let's now move to the receiver’s wallet. We want to notify the user when a new DIDComm message is received by the wallet so that the user can process it. Even though the Truvity SDK does not have the out-of-the-box notification capabilities, you can create a cron job that calls the faceted search to find all received DIDComm messages received within a specified timeframe, for example in the last 5 minutes or since the last cron update.
- TypeScript
- Java
const { id: myDid } = await client.dids.didDocumentSelfGet();
const fiveMinAgo = new Date(Date.now() - 5 * 60 * 1000);
const filterResult = await client.didcommMessages.didcommMessageSearch({
filter: [
{
data: {
to: {
operator: 'EQUAL',
value: myDid,
},
},
createdAt: {
operator: 'GREATER_THAN_OR_EQUAL',
value: fiveMinAgo,
},
},
],
});
String myDid = client.dids().didDocumentSelfGet().getId();
OffsetDateTime fiveMinAgo = OffsetDateTime.now().minusMinutes(5);
ListDidCommMessage filterResult = client.didcommMessages().didcommMessageSearch(DidcommMessageSearchRequest.builder()
.filter(List.of(
DidcommMessageFilter.builder()
.data(DidcommMessageFilterData.builder().to(DidcommMessageFilterDataTo.equal(EqualDid.builder().value(myDid).build())).build())
.createdAt(DidcommMessageFilterCreatedAt.greaterThanOrEqual(TimestampGreaterThanOrEqual.builder().value(fiveMinAgo).build()))
.build()
))
.build());
Step 5: Audit credential transactions
Goal:
Allow auditing of credential issuance and sharing for accountability and compliance.
While the Truvity platform does not provide a full end-to-end auditing solution, you can create the audit log using available SDK methods and APIs.
Business context:
Why audit credential transactions? In highly regulated industries, such as finance, healthcare, or government, businesses must be able to prove the integrity of their credentialing processes. Auditing provides a trail that can be reviewed for compliance purposes, ensuring that only authorized entities issued and accessed credentials.
Instructions
Track credential issuance
Read the latest version of the credential (by providing it's UUID) to fetch issuance details of a credential such as the DID of the issuer and the creation date.
- TypeScript
- Java
const latestVersion = await client.credentials.credentialLatest("497f6eca-6276-4993-bfeb-53cbbbba6f08");
ResourceCredential latestVersion = client.credentials().credentialLatest("497f6eca-6276-4993-bfeb-53cbbbba6f08");
Monitor credential sharing
To monitor sharing of credentials via DIDComm messaging, you can get the latest version of the DIDComm message. Use the DidcommMessageLatest
API method to retrieve the details of a previously sent DIDComm message. The call returns the details of the message including verifialbe credentials and presentations that can be added to the audit log.
- TypeScript
- Java
const result = await client.didcommMessages.didcommMessageLatest("497f6eca-6276-4993-bfeb-53cbbbba6f08");
ResourceDidCommMessage result = client.didcommMessages().didcommMessageLatest("497f6eca-6276-4993-bfeb-53cbbbba6f08");
Conclusion
In this guide, you learned how to set up and manage invitation-based custodial B2B wallets, share credentials securely via DIDComm messaging, and, most importantly, log every credential exchange transaction to ensure compliance, accountability, and trust in business-to-business interactions.
The guide covered:
- Registration and Credential Storage Creation: Organizations and departments register via the platform's signup page, which automatically creates a tenant, credential storage, and DID for each department, streamlining the onboarding process.
- Data Organization with Labels: Using labels to organize data inside single credential storage.
- Using Search to Retrieve Relevant Data: Using faceted search to retrieve resources.
- Credential Sharing and Transaction Logging: Using DIDComm messaging to securely exchange credentials between wallets, while also logging every transaction to provide verifiable proof of the exchange. This feature is critical for maintaining regulatory compliance, resolving disputes, and establishing trust between business partners.