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).
All write endpoints return an unsigned transaction. Signing always happens on the client side, never on the server.
Prerequisites
- DID, public key, and proof already generated (see DID Onboarding → steps 1 and 2).
- An EVM transaction signing library: ethers.js (Node.js),
eth_account(Python), or the internalcli-tx-savebinary if the ISBE team has provided it. - The Swagger URL of the environment (see Environments).
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
| Field | Description |
|---|---|
did | The DID generated in step 2. |
baseDocument (optional) | JSON with the @context of the DID Document. If omitted, the default context is applied. |
vMethodId | Verification method identifier. 32 random bytes in base64url (43 characters without padding). Recommended: JWK thumbprint of the public key. |
proof | ECDSA 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. |
publicKeyType | For the first registration, always secp256k1. |
publicKey | Hexadecimal uncompressed public key (with 0x04 prefix). |
notBefore | Unix timestamp (seconds) from which the key is valid. Typically now. |
notAfter | Unix timestamp of expiration. Recommended: ~10 years in the future. |
alsoKnownAs | Alternative identifier. For organizational entities use urn:oid:organizationIdentifier:<YOUR_CODE>. |
from | EOA derived from your secp256k1. |
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"]
}
]
}
}
200 OK— Transaction mined and receipt available.206 Partial Content— Transaction sent but the receipt is not yet available (case for networks with latency). Thetx_hashis 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:
- Add a P-256 key — Necessary to issue verifiable credentials.
- Create the trust chain — If the entity is going to act as a TAO or TI.
- Resolve DIDs — To verify credentials issued by third parties.