9. Signed Documents
Prerequisites:
1. Create Project entity
When creating a new project, a Strapi lifecycle hook (beforeCreate) is executed. This hook generates a new folder in
Signius and assigns its ID to the project's externalDocumentsFolderId
field. This ID is subsequently used when
creating new sign contracts.
2. Create DOCX Document(s) with Defined Fields
Currently Supported Fields:
User Data:
- firstName
- lastName
- birthDate
- phone
- street
- postalCode
- addressExtra
- city
- country
Additional fields (values passed from frontend application with createSignContracts mutation):
- amount
Inside document field needs to be added in curly braces eg.: {firstName}
Example document with placeholders:

3. Create a Contract for Each Document
- In Strapi admin, create a new Contract entity.
- Attach the DOCX document to the document field.
- Add a relation to the selected Project entity.

4. Add autoSigners to rentinvesto Single Type in strapi admin
It needs to be data of a user already created in signius and that has apiKey generated.
[
{
"name": "",
"surname": "",
"signeeEmail": "",
"signeePhone": "123456789" // optional, not needed for automatic signing
}
]
5. Add signius variables to secrects in rentinvesto Single Type in strapi admin
{
"SIGNIUS_DOCUMENT_CERT": "",
"SIGNIUS_IDENTITY_CERT": "",
"SIGNIUS_IDENTITY_PASS": "",
"SIGNIUS_IDENTITY_BASE_URL": "",
"SIGNIUS_DOCUMENTS_BASE_URL": "",
"SIGNIUS_API_KEY": "",
"SIGNIUS_AUTO_SIGN_API_KEYS": [] // apiKeys related to autoSigners data, needs to be in the same order in the list
}
Signius Integration:
Common arguments:
platform: 'RENTINVESTO' | 'CROWDEX'
1. checkSignContracts
This query should be used first, before investment process - it returns information about current sign contracts, if there are some pending or not signed - in that case we should process previously started investment process before going with the new one.
It also can return error that some user data is missing, which means he first needs to go and update data before being able to invest. Required user data:
- firstName
- lastName
- birthDate
- phone
- street
- postalCode
- addressExtra
- city
- country
query CheckSignContractsQuery($projectId: String!, $locale: String!) {
checkSignContracts(projectId: $projectId, locale: $locale) {
status
signContracts {
data {
id
attributes {
status
externalId
createdAt
signedAt
contract {
data {
attributes {
externalId
name
}
}
}
document {
data {
attributes {
url
}
}
}
}
}
}
}
}
possible status: SIGNED, NOT_SIGNED, EMPTY_CONTRACTS, PENDING
Authorization: Bearer ACCESS_TOKEN
Responses:
If all sign contracts for given user are signed:
{
"data": {
"checkSignContracts": {
"status": "SIGNED",
"signContracts": null
}
}
}
If there are no sign contracts for given user:
{
"data": {
"checkSignContracts": {
"status": "EMPTY_CONTRACTS",
"signContracts": null
}
}
}
If there are contracts that are not signed:
{
"data": {
"checkSignContracts": {
"status": "NOT_SIGNED",
"signContracts": {
"data": [
{
"id": "77",
"attributes": {
"status": "NOT_SIGNED",
"externalId": "bbe6292a-5bbb-4dad-bd22-8f79a4bda550",
"createdAt": "2024-07-22T20:24:05.813Z",
"signedAt": null,
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Document number 2"
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_5af6.pdf"
}
}
}
}
}
]
}
}
}
}
If there are contracts that are pending:
{
"data": {
"checkSignContracts": {
"status": "PENDING",
"signContracts": {
"data": [
{
"id": "77",
"attributes": {
"status": "PENDING",
"externalId": "bbe6292a-5bbb-4dad-bd22-8f79a4bda550",
"createdAt": "2024-07-22T20:24:05.813Z",
"signedAt": null,
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Document number 2"
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_5af6.pdf"
}
}
}
}
}
]
}
}
}
}
Missing user data:
{
"errors": [
{
"message": "MISSING_USER_DATA",
"extensions": {
"error": {
"name": "ApplicationError",
"message": "MISSING_USER_DATA",
"details": {}
},
"code": "STRAPI_APPLICATION_ERROR"
}
}
],
"data": null
}
2. (Option 1 - not used anymore) createSignContracts mutation is responsible for:
- check if all required userData is filled
- finding all Contract records required for given Project,
- creating new user in signius and updating user entity with signius user externalId
- creating relation between signContracts and transaction with given transactionId
- generating filled pdfs with user data (from docx template attached to related Contract entity) and uploading it to the server
- creating SignContract, ass relation to uploaded document,
- creating document instance in signius, updating it with signers data (user + admin, in that order)
- update SignContract with signus document id as externalId
mutation CreateSignContracts($projectId: String!, $locale: String!, transactionId: string, $additionalDocumentData: JSONObject!, $platform: String!) {
createSignContracts(projectId: $projectId, locale: $locale, transactionId: $transactionId, additionalDocumentData: $additionalDocumentData, platform: $platform) {
data {
id
attributes {
status
externalId
createdAt
signedAt
contract {
data {
attributes {
externalId
name
}
}
}
document {
data {
attributes {
url
}
}
}
}
}
}
}
Authorization: Bearer ACCESS_TOKEN
additionalDocumentData argument should be treated as Record<string,string>
typescript type. It is used to pass
additional data that will be used to prefill specified fields in docx template. Check in Prerequisites for supported
fields.
Responses:
Proper one:
{
"data": {
"createSignContracts": {
"data": [
{
"id": "76",
"attributes": {
"status": "NOT_SIGNED",
"externalId": "97cacdfc-373e-4dcc-8004-cdb319af63d9",
"createdAt": "2024-07-22T20:24:05.109Z",
"signedAt": null,
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Needed Document"
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_64ac.pdf"
}
}
}
}
}
]
}
}
}
Missing user data:
{
"errors": [
{
"message": "MISSING_USER_DATA",
"extensions": {
"error": {
"name": "ApplicationError",
"message": "MISSING_USER_DATA",
"details": {}
},
"code": "STRAPI_APPLICATION_ERROR"
}
}
],
"data": null
}
2. (Option 2 - used in crowdex/rentinvesto webapp ) - createPurchase mutation
Same as Option 1, these mutation apart for creating transaction is responsible for:
- check if all required userData is filled
- finding all Contract records required for given Project, then:
If there are some contracts for given project:
- creating new user in signius and updating user entity with signius user externalId
- creating relation between signContracts and transaction with given transactionId
- generating filled pdfs with user data (from docx template attached to related Contract entity) and uploading it to the server
- creating SignContract, ass relation to uploaded document,
- creating document instance in signius, updating it with signers data (user + admin, in that order)
- update SignContract with signus document id as externalId
- setting transaction in REQUIRES_CONTRACTS_SIGNING state
If there are no contracts for given project:
- setting transaction in PENDING state
mutation createTransaction(
$projectId: String!
$amount: Float!
$locale: String!
$additionalDocumentData: JSONObject!
) {
createPurchase(
projectId: $projectId
platform: "RENTINVESTO" // -> RENTINVESTO or CROWDEX
amount: $amount
locale: $locale
additionalDocumentData: $additionalDocumentData
) {
correlationId
signContracts {
data {
id
attributes {
status
externalId
createdAt
signedAt
contract {
data {
attributes {
externalId
name
project {
data {
attributes {
title
}
}
}
}
}
}
document {
data {
attributes {
url
}
}
}
}
}
}
}
}
Authorization: Bearer ACCESS_TOKEN
additionalDocumentData argument should be treated as Record<string,string>
typescript type. It is used to pass
additional data that will be used to prefill specified fields in docx template. Check in Prerequisites for supported
fields.
3. createSigningSession mutation is used to create signing session in signus, it will send SMS to user phone number with code to sign the document.
mutation CreateSigningSession($signContractIds: [String!]!, $platform: String!) {
createSigningSession(signContractIds: $signContractIds, platform: $platform) {
batchIds
}
}
Authorization: Bearer ACCESS_TOKEN
signContractIds - list of signContracts ids, which will be signed during this session
Responses:
{
"data": {
"createSigningSession": {
"batchIds": ["b42fecb0-0cb5-43f2-aaea-0e199a2e33d5"]
}
}
}
batchIds - these are used in next mutation to process contract signing
Calling for sign contracts that were already signed:
{
"errors": [
{
"message": "CONTRACTS_ALREADY_SIGNED",
"extensions": {
"error": {
"name": "ConflictError",
"message": "CONTRACTS_ALREADY_SIGNED"
},
"code": "STRAPI_CONFLICT_ERROR"
}
}
],
"data": {
"createSigningSession": null
}
}
4. processSigning mutation is used to sign documents with code from SMS
mutation ProcessSigning($batchIds: [String!]!, $signContractIds: [String!]!, $code: String!, $platform: String!) {
processSigning(batchIds: $batchIds, signContractIds: $signContractIds, code: $code, platform: $platform) {
status
}
}
Authorization: Bearer ACCESS_TOKEN
signContractIds - list of signContracts ids, the same that were used in createSigningSession mutation - it is needed so we know which sign contracts status should be changed to PENDING
batchIds - response from the createSigningSession mutation
code - sms code
Responses:
If everything went ok:
{
"data": {
"processSigning": {
"status": "OK"
}
}
}
When wrong sms code has been provided:
{
"errors": [
{
"message": "INCORRECT_CODE",
"extensions": {
"error": {
"name": "ApplicationError",
"message": "INCORRECT_CODE",
"details": {}
},
"code": "STRAPI_APPLICATION_ERROR"
}
}
],
"data": {
"processSigning": null
}
}
5. signedContractsByIds query - used to retrieve sign contracts by ids, the response is diferent for each status. This query should be used for polling to check status of contracts after user signed contracts with sms code.
query SignedContractsByIds($signContractIds: [String!]!) {
signedContractsByIds(signContractIds: $signContractIds) {
status
signContracts {
data {
id
attributes {
status
signedAt
contract {
data {
attributes {
externalId
name
}
}
}
document {
data {
attributes {
url
}
}
}
}
}
}
}
}
Authorization: Bearer ACCESS_TOKEN
signContractIds - list of signContracts ids, which we want to get
Responses:
If all contracts are signed:
{
"data": {
"signedContractsByIds": {
"status": "SIGNED",
"signContracts": {
"data": [
{
"id": "75",
"attributes": {
"status": "SIGNED",
"signedAt": "2024-07-22T19:36:16.132Z",
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Needed Document"
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_8515373f04_signed.pdf"
}
}
}
}
}
]
}
}
}
}
If some contracts are in PENDING state:
{
"data": {
"checkSignContracts": {
"status": "PENDING",
"signContracts": null
}
}
}
If there are no sign contracts with given ids:
{
"data": {
"checkSignContracts": {
"status": "EMPTY_CONTRACTS",
"signContracts": null
}
}
}
If there are sign contracts that are with NOT_SIGNED status (but there are not with PENDING state):
{
"data": {
"signedContractsByIds": {
"status": "NOT_SIGNED",
"signContracts": {
"data": [
{
"id": "77",
"attributes": {
"status": "NOT_SIGNED",
"signedAt": null,
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Document number 2"
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_5af6.pdf"
}
}
}
}
}
]
}
}
}
}
6. allSignContracts query - returns all signContracts for given user with chosen project data
query AllSignContracts {
allSignContracts {
data {
id
attributes {
status
externalId
createdAt
signedAt
contract {
data {
attributes {
externalId
name
project {
data {
id
attributes {
title
}
}
}
crowdexProject {
data {
id
attributes {
title
}
}
}
}
}
}
document {
data {
attributes {
url
}
}
}
}
}
}
}
Authorization: Bearer ACCESS_TOKEN
Response:
{
"data": {
"allSignContracts": {
"data": [
{
"id": "70",
"attributes": {
"status": "SIGNED",
"externalId": "cf33bf51-aa66-425d-838c-a6345297c677",
"createdAt": "2024-07-22T18:43:42.559Z",
"signedAt": null,
"contract": {
"data": {
"attributes": {
"externalId": null,
"name": "Needed Document",
"project": {
"data": {
"id": "2",
"attributes": {
"title": "ProjectWithSignus"
}
}
},
"crowdexProject": {
"data": {
"id": "5",
"attributes": {
"title": "Great project with Signus"
}
}
}
}
}
},
"document": {
"data": {
"attributes": {
"url": "/uploads/Crowdex_Test_Document_6c059d0732_9_625e921102_signed_signed_signed_signed.pdf"
}
}
}
}
}
]
}
}
}
removeSignContracts mutation is used to delete signContracts records, remove documents from the server and remove documents on signius side
mutation RemoveSignContracts($platform: String!, $signContractIds: [String!]!) {
removeSignContracts(platform: $platform, signContractIds: $signContractIds) {
status
}
}
Authorization: Bearer ACCESS_TOKEN
signContractIds - list of signContracts ids, which we want to delete
Response:
{
"data": {
"removeSignContracts": {
"status": "OK"
}
}
}
Signius webhook:
Url: {backendBaseUrl}/api/signius/webhook
Authroization: No Auth
Response body:
- After user signed the document:
{
"documentId": "55609398-22b5-4a1f-89a8-66049d93ac84", --> SignContract externalId
"presentSignee": "69dcbd9d-47e6-4cc1-891a-29f5f8e80a04", --> User externalId
"nextSignee": "", --> not important in our flow, because we are using automatic signing for the rest of the signers
"documentStatus": "SIGNING_IN_PROGRESS"
}
In webhook method following operations are happening:
- automatic signing in signius - for each apiKey from SIGNIUS_AUTO_SIGN_API_KEYS secrets variable
- current document for SignContract entity is replaced with the new one (signed, downloaded from signius)
- SignContract entity status is changed to SIGNED
- Related Transaction status is changed from 'REQUIRES_CONTRACTS_SIGNING' to 'PENDING'