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.
// 1. Listen to a verification requestoverridefunonMessage(message:Message){when(message){isVerificationRequest->{Log.d(LOGTAG,"received verification message")request=message}}}// 2. Show the UI for the request and let users accept or reject it.account.DisplayRequestUI(selfModifier,requestasVerificationRequest,onFinish={isSuccess,status->request=null})
// Self SDK: Complete document signing flow (starting from document reception)// 1. RECEIVE DOCUMENT FROM SERVERfunconMessage(message:anyself_ios_sdk.Message){print("onMessage: \(message)")switchmessage{caseisVerificationRequest:letverificationRequest=messageas!VerificationRequestdefault:print("🎯 ContentView: ❓ Unknown message type: \(type(of:message))")break}}funconDisconnect(errorMessage:String?){}funconError(id:String,errorMessage:String?){}// 2. SIGN DOCUMENT (when user approves)// Show the UI for the request and let users accept or reject it. The UI is rendered by the Self SDK.self_ios_sdk.MessageView(account:account,message:verificationRequest){resultinswitchresult{case.success(letstatus):ifstatus==MessageStatus.accepted.rawValue{print("Verification request accepted!")}elseifstatus==MessageStatus.rejected.rawValue{print("Verification request rejected!")}case.failure(leterror):print("Action failed: \(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