Skip to main content

DID Registration via the API

This page covers steps 3 to 6 of the onboarding: create the insertion transaction, sign it off-chain, and send it to the network. It is the direct path for integrators who already have the DID and public key generated (steps 1 and 2 from DID Onboarding).

The DID Registry API does not custody private keys

All write endpoints return an unsigned transaction. Signing always happens on the client side, never on the server.

Prerequisites

3. Create the Insertion Transaction

Open the Swagger UI of the environment and locate the operation:

POST /api/v1/contract/insertFirstDidDocument

Request body:

{
"did": "did:isbe:uc:z12kVar5p6bD5WCjEJcQ18Zyt8XV",
"vMethodId": "9lfZ28jdUsYo75lVHmGNIc6oUpkjSFtxiCLjDGn28IM",
"proof": "0x70bc9d8cdf399369f0b213a7da5985e5cd533d85bb5562615c98bfb5ac33d40e491436a3086a87ccaae773cbc20a098ef70928dc24b2c636dc8413965f2959a51c",
"publicKeyType": "secp256k1",
"publicKey": "0x041243f50ac70fc996ccc92a2119ce64b6f70bbf4ca1fd3b292fffa8939d06c4f75143e0f7e23b32139cce3a33d2207f937c0b85714d207877e3eca7137dc33214",
"notBefore": 1774602263,
"notAfter": 2090221463,
"alsoKnownAs": "urn:oid:organizationIdentifier:ISBE",
"from": "0xAe0E493C67f75381b0954644836A3C16c79D0dFD"
}

Meaning of Each Field

FieldDescription
didThe DID generated in step 2.
baseDocument (optional)JSON with the @context of the DID Document. If omitted, the default context is applied.
vMethodIdVerification method identifier. 32 random bytes in base64url (43 characters without padding). Recommended: JWK thumbprint of the public key.
proofECDSA signature with the secp256k1 private key over the material that binds the DID with the public key. It is returned directly by did-gen did and proves to the smart contract that whoever sends the transaction actually controls the key they intend to register under that DID.
publicKeyTypeFor the first registration, always secp256k1.
publicKeyHexadecimal uncompressed public key (with 0x04 prefix).
notBeforeUnix timestamp (seconds) from which the key is valid. Typically now.
notAfterUnix timestamp of expiration. Recommended: ~10 years in the future.
alsoKnownAsAlternative identifier. For organizational entities use urn:oid:organizationIdentifier:<YOUR_CODE>.
fromEOA derived from your secp256k1.
Quick calculation of timestamps

Use epochconverter.com to obtain notBefore (now) and notAfter (future).

Response

The API returns an unsigned transaction ready to be signed:

{
"to": "0x00000000000000000000000000000000000015Be",
"data": "0x648b2748...",
"gasLimit": "0x4c4b40",
"gasPrice": "0xfa0",
"chainId": "0x8131",
"nonce": "0xe",
"value": "0x0",
"from": "0xAe0E493C67f75381b0954644836A3C16c79D0dFD"
}

Copy it entirely for the next step.

4. Sign the Transaction

The transaction returned by the API already comes with all the necessary fields (to, data, gasLimit, gasPrice, chainId, nonce, value, from) in JSON-RPC hexadecimal format. You only need to sign it with the private key of the from EOA. Below are two equivalent examples — use the one that fits your stack.

Option A — Node.js with ethers.js

npm install ethers
import { Wallet, Transaction } from "ethers";

const PRIVATE_KEY = "0x<PRIVATE_KEY>";
const raw = {
to: "0x00000000000000000000000000000000000015Be",
data: "0x648b2748...",
gasLimit: "0x4c4b40",
gasPrice: "0xfa0",
chainId: "0x8131",
nonce: "0xe",
value: "0x0",
from: "0xAe0E493C67f75381b0954644836A3C16c79D0dFD",
};

const wallet = new Wallet(PRIVATE_KEY);
// `from` is not part of the serialized tx — it is discarded before signing
const tx = Transaction.from({ ...raw, from: undefined });
const signedRawTransaction = await wallet.signTransaction(tx);

console.log(signedRawTransaction);
// 0x01f9034c8281310e820fa0834c4b40...

This is exactly the pattern used by the internal scripts in the isbe-identity-did-api repository (scripts/addVM.ts, scripts/initialize.ts).

Option B — Python with eth_account

pip install eth-account
from eth_account import Account

PRIVATE_KEY = "0x<PRIVATE_KEY>"
raw = {
"to": "0x00000000000000000000000000000000000015Be",
"data": "0x648b2748...",
"gasLimit": "0x4c4b40",
"gasPrice": "0xfa0",
"chainId": "0x8131",
"nonce": "0xe",
"value": "0x0",
}

acct = Account.from_key(PRIVATE_KEY)
# eth_account uses the snake_case names expected by web3.py
tx = {
"to": raw["to"],
"data": raw["data"],
"gas": int(raw["gasLimit"], 16),
"gasPrice": int(raw["gasPrice"], 16),
"chainId": int(raw["chainId"], 16),
"nonce": int(raw["nonce"], 16),
"value": int(raw["value"], 16),
}
signed = acct.sign_transaction(tx)
print(signed.raw_transaction.hex())
# 01f9034c8281310e820fa0834c4b40...

Option C — Internal cli-tx-save CLI

If the ISBE team has provided you with the cli-tx-save binary, the equivalent command is:

./sign-tx sign -p 0x<PRIVATE_KEY> -t '<RESPONSE_BODY>'

Where <RESPONSE_BODY> is the complete JSON response from step 3, in single quotes.

Result

In any case, you obtain a signed raw transaction like:

0x01f9034c8281310e820fa0834c4b40...

Save that complete string: it is what you will need in the next step.

5. Send the Transaction

In the Swagger UI, locate:

POST /api/v1/transactions/send

Body:

{
"signedRawTransaction": "0x01f9034c8281310e820fa0834c4b40..."
}

Expected response (success):

{
"tx_hash": "0xb508e46adfc36822e48dde1d9f1f5d2122a1ce6852cf5e0afe6c978da04648c5",
"receipt": {
"blockHash": "0x52afb6108087cdca826289da40cc7339a4e190922f8480afd924c0ddd9d31109",
"blockNumber": 3907283,
"status": 1,
"from": "0xAe0E493C67f75381b0954644836A3C16c79D0dFD",
"to": "0x00000000000000000000000000000000000015Be",
"gasUsed": "221678",
"logs": [
{
"address": "0x00000000000000000000000000000000000015Be",
"topics": ["0x78c431540f7778eb787241a03d7401cd2c95ddb232c918f172f86e4c804d5439"]
}
]
}
}
Possible responses
  • 200 OK — Transaction mined and receipt available.
  • 206 Partial Content — Transaction sent but the receipt is not yet available (case for networks with latency). The tx_hash is already valid for queries.
  • 400 Bad Request — The transaction could not be processed. Review the signature, nonce, and funds.

6. Verify the Result

To confirm that the DID is registered correctly, resolve the DID Document:

GET /api/v1/identifiers/{did}

Call with Accept: application/did+ld+json or application/did+json. The response must include your newly registered verificationMethod.

curl -H "Accept: application/did+ld+json" \
https://did-registry.portal.redisbe.com/api/v1/identifiers/did:isbe:uc:z12kVar5p6bD5WCjEJcQ18Zyt8XV

Next Steps

Once the DID is registered: