Self provides a secure digital agreement signing workflow that allows users to cryptographically sign documents. This workflow ensures document integrity, non-repudiation, and tamper-proof signatures that can be verified independently. The digital signing process is designed to be used in a variety of scenarios, such as:
Legal contract signing for business applications
Terms of service acceptance for web and mobile platforms
Start a server - Use the Docker links above to run a Golang or Java server
Install mobile app - Download the Android or iOS app using the links above
Register Self account - Open the mobile app and follow the registration prompts
Connect to server - Enter the server identifier into the mobile app to establish connection
Test document signing - Follow the prompts in the mobile app to sign a document
Each application includes complete document signing workflows with real cryptographic signatures, verification, and audit trails.
How to build a digital agreement signing workflow¶
You can build a secure digital agreement signing workflow with Self using verification requests and responses. Let's break down the process into steps:
serverAddress:=inboxAddressclientAddress:=msg.FromAddress()pdf:=fpdf.New("P","mm","A4","")pdf.AddPage()pdf.SetFont("Arial","B",16)pdf.Cell(40,10,"Document Signing Agreement")pdf.Ln(20)pdf.SetFont("Arial","",12)pdf.Cell(0,10,"This document represents an agreement between:")pdf.Ln(10)pdf.Cell(0,10,"Server: "+serverAddress.String())pdf.Ln(10)pdf.Cell(0,10,"Client: "+clientAddress.String())pdf.Ln(10)pdf.Cell(0,10,"By signing this agreement, both parties acknowledge")pdf.Ln(10)pdf.Cell(0,10,"the terms and conditions of this document signing process.")agreementBuf:=bytes.NewBuffer(make([]byte,1024))err:=pdf.Output(agreementBuf)iferr!=nil{log.Fatalf("Failed to generate PDF: %v",err)}agreementTerms,err:=object.New("application/pdf",agreementBuf.Bytes())iferr!=nil{log.Fatalf("Failed to create agreement object: %v",err)}err=selfAccount.ObjectUpload(agreementTerms,false)iferr!=nil{log.Fatalf("Failed to upload agreement object: %v",err)}claims:=map[string]interface{}{"termsHash":hex.EncodeToString(agreementTerms.Hash()),"parties":[]map[string]interface{}{{"type":"signatory","id":serverAddress.String()},{"type":"signatory","id":clientAddress.String()},},}unsignedAgreementCredential,err:=credential.NewCredential().CredentialType([]string{"VerifiableCredential","AgreementCredential"}).CredentialSubject(credential.AddressKey(serverAddress)).CredentialSubjectClaims(claims).CredentialSubjectClaim("terms",hex.EncodeToString(agreementTerms.Id())).Issuer(credential.AddressKey(serverAddress)).ValidFrom(time.Now()).SignWith(serverAddress,time.Now()).Finish()iferr!=nil{log.Fatalf("Failed to create credential: %v",err)}signedAgreementCredential,err:=selfAccount.CredentialIssue(unsignedAgreementCredential)iferr!=nil{log.Fatalf("Failed to issue credential: %v",err)}unsignedAgreementPresentation,err:=credential.NewPresentation().PresentationType([]string{"VerifiablePresentation","AgreementPresentation"}).Holder(credential.AddressKey(serverAddress)).CredentialAdd(signedAgreementCredential).Finish()iferr!=nil{log.Fatalf("Failed to create presentation: %v",err)}signedAgreementPresentation,err:=selfAccount.PresentationIssue(unsignedAgreementPresentation)iferr!=nil{log.Fatalf("Failed to issue presentation: %v",err)}content,err:=message.NewCredentialVerificationRequest().Type([]string{"VerifiableCredential","AgreementCredential"}).Evidence("terms",agreementTerms).Proof(signedAgreementPresentation).Expires(time.Now().Add(time.Hour*24)).Finish()iferr!=nil{log.Fatalf("Failed to build verification request: %v",err)}err=selfAccount.MessageSend(msg.FromAddress(),content)iferr!=nil{log.Fatalf("Failed to send document signing request to %s: %v",msg.FromAddress(),err)}else{log.Printf("Successfully sent document signing request to: %s",msg.FromAddress())}
You need to listen to the verification request in the account's callback and then check the incoming message for its type and details. If the request is for document signing, display the document content to the user for review and signature.
// Self SDK: Complete document signing flow (starting from document reception)// 1. RECEIVE DOCUMENT FROM SERVERaccount.setOnRequestListener{msg->when(msg){isVerificationRequest->{if(msg.types().contains(CredentialType.Agreement)){verificationRequest=msg// Document received and stored}}}}// 2. SIGN DOCUMENT (when user approves)funsignDocument(){if(verificationRequest==null)return// Self SDK: Create signed responsevalverificationResponse=VerificationResponse.Builder().setRequestId(verificationRequest!!.id()).setTypes(verificationRequest!!.types()).setToIdentifier(verificationRequest!!.toIdentifier()).setFromIdentifier(verificationRequest!!.fromIdentifier()).setStatus(ResponseStatus.accepted)// User approved the document.build()// 3. RETURN SIGNED DOCUMENT TO SERVERaccount.send(verificationResponse){messageId,_->// Document has been cryptographically signed and sent back to server}}
// Self SDK: Complete document signing flow (starting from document reception)// 1. RECEIVE DOCUMENT FROM SERVERaccount.setOnRequestListener{messageinswitchmessage{caseisVerificationRequest:letverificationRequest=messageas!VerificationRequestcurrentVerificationRequest=verificationRequest// Document received and stored}}// 2. SIGN DOCUMENT (when user approves)funcsignDocument(){guardletverificationRequest=currentVerificationRequestelse{return}// Self SDK: Create signed responseletverificationResponse=VerificationResponse.Builder().withRequestId(verificationRequest.id()).toIdentifier(verificationRequest.toIdentifier()).fromIdentifier(verificationRequest.fromIdentifier()).withTypes(verificationRequest.types()).withStatus(ResponseStatus.accepted)// User approved the document.withCredentials([])// Optional: additional credentials if needed.build()// 3. RETURN SIGNED DOCUMENT TO SERVERself.sendKMPMessage(message:verificationResponse){messageId,errorin// Document has been cryptographically signed and sent back to server}}// Self SDK: Helper method to send messagesfuncsendKMPMessage(message:Message,completion:((_messageId:String,_error:Error?)->())?=nil){Task(priority:.background,operation:{tryawaitself.account.send(message:message,onAcknowledgement:{msgId,errorincompletion?(msgId,error)})})}
response,err:=message.DecodeCredentialVerificationResponse(msg.Content())iferr!=nil{log.Printf("Failed to decode verification response from %s: %v",msg.FromAddress(),err)return}switchresponse.Status(){casemessage.ResponseStatusAccepted,message.ResponseStatusCreated:log.Printf("Client %s has digitally signed the agreement",msg.FromAddress())casemessage.ResponseStatusUnauthorized,message.ResponseStatusForbidden,message.ResponseStatusNotAcceptable:log.Printf("Client %s declined to sign the agreement",msg.FromAddress())default:log.Printf("UNKNOWN RESPONSE STATUS from client: %s",msg.FromAddress())}
ContentType.CREDENTIAL_VERIFICATION_RESPONSE->{valverificationResponse=CredentialVerificationResponse.decode(content)println("Response received with status:${verificationResponse.status().name}")verificationResponse.credentials().forEach{cred->valclaims=cred.credentialSubjectClaims()claims.forEach{println("types:${cred.credentialType().toList()}"+"\nfield:${it.key}"+"\nvalue:${it.value}")println()}}}
This example demonstrates how to build a comprehensive digital agreement signing workflow with Self. We've seen how to send verification requests for document signing, handle the signing process on mobile clients, validate signature responses, and maintain audit trails for compliance.
The Self SDK provides a secure, legally-compliant foundation for digital document signing that ensures:
Identity verification: Signers are authenticated using their Self digital identity