Writing a custom notary service (experimental)

Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it with @CordaService. The Corda node scans for any class with this annotation and initialises them. The custom notary service class should provide a constructor with two parameters of types AppServiceHub and PublicKey.

class MyCustomValidatingNotaryService(override val services: AppServiceHub, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
    override val uniquenessProvider = PersistentUniquenessProvider()

    override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = MyValidatingNotaryFlow(otherPartySession, this)

    override fun start() {}
    override fun stop() {}


The next step is to write a notary service flow. You are free to copy and modify the existing built-in flows such as ValidatingNotaryFlow, NonValidatingNotaryFlow, or implement your own from scratch (following the NotaryFlow.Service template). Below is an example of a custom flow for a validating notary service:

class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidatingNotaryService) : NotaryFlow.Service(otherSide, service) {
     * The received transaction is checked for contract-validity, for which the caller also has to to reveal the whole
     * transaction dependency chain.
    override fun receiveAndVerifyTx(): TransactionParts {
        try {
            val stx = receiveTransaction()
            val notary = stx.notary
            val timeWindow: TimeWindow? = if (stx.coreTransaction is WireTransaction) stx.tx.timeWindow else null
            return TransactionParts(stx.id, stx.inputs, timeWindow, notary!!)
        } catch (e: Exception) {
            throw when (e) {
                is TransactionVerificationException,
                is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
                else -> e

    private fun receiveTransaction(): SignedTransaction {
        return otherSideSession.receive<NotarisationPayload>().unwrap {
            val stx = it.signedTransaction
            validateRequest(NotarisationRequest(stx.inputs, stx.id), it.requestSignature)

    private fun resolveAndContractVerify(stx: SignedTransaction) {
        subFlow(ResolveTransactionsFlow(stx, otherSideSession))
        stx.verify(serviceHub, false)

    private fun verifySignatures(stx: SignedTransaction) {
        val transactionWithSignatures = stx.resolveTransactionWithSignatures(serviceHub)

    private fun checkSignatures(tx: TransactionWithSignatures) {
        try {
        } catch (e: SignatureException) {
            throw NotaryInternalException(NotaryError.TransactionInvalid(e))

    private fun customVerify(stx: SignedTransaction) {
        // Add custom verification logic


To enable the service, add the following to the node configuration:

notary : {
    validating : true # Set to false if your service is non-validating
    custom : true