Skip to main content

Register a Service DID

This page explains, step by step, how to register a Service DID under your organisational DID. If you do not yet know what a Service DID is, start with the introduction.

Overview​

Private keys are only used on your computer, inside the CLI. The API never sees them.

Prerequisites​

Before you start you need:

  1. A registered organisational DID in the environment where you will register the service. It will be the umbrella DID. If you do not have one yet, follow the DID onboarding.
  2. The umbrella DID's keystore (for example, identity.keystore.json) and its passphrase. It is the file you generated with did-gen keys when you created your DID.
  3. The isbe-identity-did-gen CLI, version 3.0.0 or later. ⬇ Download the CLI (ZIP).
  4. Node.js 20 or later and npm 10 or later, to run the CLI.
  5. The Service DID Registry API URL for the environment. You will find it in Environments.
Environment

The examples use pre-production (uc-pre). For production, use uc and the production URL.

Step 1 — Install the CLI​

Unzip the ZIP you downloaded in the prerequisites. It creates an isbe-identity-did-gen-3.0.0 folder. Go into it and install the CLI:

cd isbe-identity-did-gen-3.0.0
npm ci
npm run build
npm link

npm link makes the did-gen command available from any folder. Check that it works:

did-gen --version
If npm link fails with EACCES: permission denied

This is a permissions issue with npm's global folder, common on macOS. The CLI's README explains how to fix it. You can also skip npm link and run the CLI from its folder with node dist/index.js instead of did-gen.

Step 2 — Get the umbrella DID's details​

You need two details of your umbrella DID: the DID and its EOA (the address of the account that signs your transactions). You get them from its keystore:

did-gen did --keystore ./identity.keystore.json --modelDeploy uc-pre

It asks for the keystore passphrase and prints:

DID:
did:isbe:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk

Public Key:
0x04…

Proof:
0x…

EOA: 0xB8172459924a1e81E511D6473f923E531f7B3a5B

Note down the DID and the EOA.

Use the right environment

--modelDeploy must be the environment where you registered your DID: uc-pre in pre-production. In production, leave --modelDeploy out (it defaults to uc). With the wrong environment you get a different DID, which is not yours.

Step 3 — Generate the service key​

Generate a new key pair for the service, in a different keystore from your umbrella DID's:

mkdir -p keys
did-gen keys --out ./keys/facturacion.keystore.json

Choose a passphrase for this keystore. Among other details, the command prints:

Public Key (hex):
0x04ad5f…(130 characters in total)

Note down the Public Key (hex): it is the key you will register for the service.

Do not use the umbrella DID's key

The service key must be different from your organisational DID's key. If you use the umbrella's key, the registration is rejected. You can reuse the key of another service of your own umbrella, but never the key of an organisational DID or of another organisation's service.

Keep facturacion.keystore.json and its passphrase safe. It is the key the service will sign its own work with. You do not need it to register the service: the registration is signed by the umbrella DID.

Step 4 — Request the registration transaction​

Open the Swagger UI of the environment's Service DID Registry API (see Environments) and find the operation:

POST /api/v1/contract/registerServiceDid

Minimum body​

{
"controllerDid": "did:isbe:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk",
"publicKey": "0x04<service public key>",
"publicKeyType": "secp256k1",
"label": "facturacion",
"expiresAt": 0,
"from": "0xB8172459924a1e81E511D6473f923E531f7B3a5B"
}
FieldWhat to put
controllerDidThe umbrella DID from step 2.
publicKeyThe service public key from step 3. It starts with 0x04.
publicKeyTypesecp256k1.
labelA short name for you to recognise the service, for example facturacion. Only its digest (hash) is stored on-chain, not the text.
expiresAtExpiry date in Unix seconds. 0 means it never expires.
fromThe umbrella DID's EOA from step 2.

Full body, with free-form fields​

Any field you add on top of the ones above is stored on-chain and appears in the service's DID Document (see Free-form fields). For example:

{
"controllerDid": "did:isbe:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk",
"publicKey": "0x04<service public key>",
"publicKeyType": "secp256k1",
"label": "facturacion-electronica",
"expiresAt": 0,
"from": "0xB8172459924a1e81E511D6473f923E531f7B3a5B",

"name": "E-invoicing service",
"description": "Issuing and receiving electronic invoices (Facturae and UBL) for business customers.",
"serviceType": "InvoicingService",
"version": "2.1.0",
"tags": ["invoicing", "facturae", "ubl", "b2b"],
"alsoKnownAs": ["https://facturacion.ejemplo.es"],
"organization": {
"legalName": "Ejemplo Soluciones S.A.",
"taxId": "A12345678",
"country": "ES",
"website": "https://www.ejemplo.es"
},
"contact": {
"email": "soporte.facturacion@ejemplo.es"
},
"service": [
{
"id": "#api",
"type": "InvoicingAPI",
"serviceEndpoint": "https://api.facturacion.ejemplo.es/v2"
},
{
"id": "#openapi",
"type": "OpenAPISpecification",
"serviceEndpoint": "https://api.facturacion.ejemplo.es/v2/openapi.json"
},
{
"id": "#vc-issuer",
"type": "CredentialIssuer",
"serviceEndpoint": "https://facturacion.ejemplo.es/.well-known/openid-credential-issuer"
}
],
"termsOfService": "https://www.ejemplo.es/legal/condiciones-facturacion"
}

The service ids (#api, #openapi…) are relative to the Service DID itself: #api stands for did:isbe:svc:…:3#api.

Response​

The API returns the unsigned transaction:

{
"to": "0x00000000000000000000000000000000000015Be",
"data": "0x2cc72092…",
"gasLimit": "0x65bc8",
"gasPrice": "0xfa0",
"chainId": "0x…",
"nonce": "0x1",
"value": "0x0",
"from": "0xB8172459924a1e81E511D6473f923E531f7B3a5B"
}

Copy the whole response and save it in a file named unsigned-tx.json, in the folder you will run the CLI from.

Step 5 — Sign the transaction​

The transaction is signed by the umbrella DID, with its keystore:

did-gen sign-tx --keystore ./identity.keystore.json --tx-file unsigned-tx.json > signed.hex

It asks for the umbrella keystore's passphrase. Before signing, the CLI checks that the transaction belongs to your account: if from does not match your keystore, it does not sign it.

The signed transaction is now in signed.hex. To copy it without mistakes, copy it from the file rather than selecting it in the terminal:

cat signed.hex | pbcopy

pbcopy is macOS only. On Linux you can use xclip -selection clipboard < signed.hex, or open the file and copy its content.

Copy the whole signature

If a single character is lost when copying it, the API replies invalid BytesLike value and the transaction is not sent. Copying it from signed.hex avoids this.

Step 6 — Send the transaction​

In the Swagger UI, find:

POST /api/v1/transactions/send

And send:

{
"signedRawTransaction": "0x01f9…"
}

The response includes the transaction hash (tx_hash) and its receipt (receipt):

  • 200: the transaction has been processed. If receipt.status is 1, the Service DID is registered.
  • 206: the transaction has been sent, but its receipt was not ready yet. Wait a few seconds and check it in step 7.

Step 7 — Check the Service DID​

Find its identifier​

List the Service DIDs of your umbrella:

GET /api/v1/service-dids?controller=<umbrella DID>

If you use curl, URL-encode the DID (: becomes %3A):

curl -s "<API>/api/v1/service-dids?controller=did%3Aisbe%3Auc-pre%3Az1MsSK9H41YvdCrJPmPycEqijYbk"

The new service is the one with the highest nonce. Its identifier is in serviceDid:

{
"items": [
{
"serviceDid": "did:isbe:svc:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk:3",
"controllerDid": "did:isbe:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk",
"signingKeyAddress": "0x…",
"nonce": "3",
"deactivated": false,
…
}
],
"total": "3",
…
}

Resolve the DID Document​

GET /api/v1/identifiers/did:isbe:svc:uc-pre:z1MsSK9H41YvdCrJPmPycEqijYbk:3

Check that:

  • controller is your umbrella DID.
  • verificationMethod holds the service key.
  • Your free-form fields are there, if you added any.
  • There is no "deactivated": true.

Common errors​

ErrorCauseFix
403 FORBIDDEN — 'from' is not an active controller of 'controllerDid'The from account does not control the umbrella DID.Use the umbrella DID's EOA (step 2).
400 — 'controllerDid' is not validThe umbrella DID is misspelt or belongs to another environment (for example, a uc-dev DID in pre-production).Check the DID and the --modelDeploy in step 2.
400 — The service document cannot set …You used a reserved field among the free-form fields.Remove or rename that field (see Free-form fields).
400 — The service document is … bytes; the registry accepts at most 4096.The free-form fields exceed 4096 bytes.Reduce the free-form fields.
400 — request/body must have required property …A required field is missing or has the wrong format.Check the body against the table in step 4.
500 — invalid BytesLike valueThe signed transaction is incomplete: a character was lost when copying it.Copy it again from signed.hex.
500 — transaction execution revertedThe contract rejected the registration. Most often: the service key is the umbrella DID's key or another organisation's service key.Generate a new key for the service (step 3) and repeat from step 4.
500 — Known transactionThat transaction had already been sent.Check in step 7 whether the service is already registered.
500 — nonce too lowThe transaction was requested before a previous one from the same account had been processed.Request a new transaction (step 4) and sign it again.

Next steps​