const { Documenso } = require("@documenso/sdk-typescript"); const fs = require("fs"); const path = require("path"); const logger = require("../utils/logger"); const { QUERY_META_FOR_ESIG_COMPLETION, INSERT_ESIGNATURE_DOCUMENT, INSERT_ESIG_AUDIT_TRAIL } = require("../graphql-client/queries"); const { uploadFileBuffer } = require("../media/imgproxy-media"); const client = require('../graphql-client/graphql-client').client; const documenso = new Documenso({ apiKey: "api_asojim0czruv13ud",//Done on a by team basis, serverURL: "https://stg-app.documenso.com/api/v2", }); const webhookTypeEnums = { DOCUMENT_CREATED: "DOCUMENT_CREATED", DOCUMENT_SENT: "DOCUMENT_SENT", DOCUMENT_COMPLETED: "DOCUMENT_COMPLETED", DOCUMENT_REJECTED: "DOCUMENT_REJECTED", DOCUMENT_CANCELLED: "DOCUMENT_CANCELLED", DOCUMENT_OPENED: "DOCUMENT_OPENED", DOCUMENT_SIGNED: "DOCUMENT_SIGNED", } async function esignWebhook(req, res) { console.log("Esign Webhook Received:", req.body); try { const message = req.body logger.log(`esig-webhook-received`, "DEBUG", "redis", "api", { event: message.event, body: message }); switch (message.event) { case webhookTypeEnums.DOCUMENT_CREATED: //This is largely a throwaway event we know it was created. console.log("Document created event received. Document ID:", message.payload.documentId); // Here you can add any additional processing you want to do when a document is created break; case webhookTypeEnums.DOCUMENT_COMPLETED: console.log("Document completed event received. Document ID:", message.payload.documentId); await handleDocumentCompleted(message.payload); // Here you can add any additional processing you want to do when a document is completed break; case webhookTypeEnums.DOCUMENT_SIGNED: console.log("Document signed event received. Document ID:", message.payload.documentId); // Here you can add any additional processing you want to do when a document is signed break; default: console.log(`Unhandled event type: ${message.event}`); } // const result = await documenso.documents.download({ // documentId: req.body.payload.id, // }); // result.resultingBuffer = Buffer.from(result.resultingArrayBuffer); // // Save the document to a file for testing purposes // const downloadsDir = path.join(__dirname, '../downloads'); // if (!fs.existsSync(downloadsDir)) { // fs.mkdirSync(downloadsDir, { recursive: true }); // } // const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`); // fs.writeFileSync(filePath, result.resultingBuffer); // console.log(result) res.sendStatus(200) } catch (error) { logger.log(`esig-webhook-error`, "ERROR", "redis", "api", { message: error.message, stack: error.stack, body: req.body }); // const downloadsDir = path.join(__dirname, '../downloads'); // if (!fs.existsSync(downloadsDir)) { // fs.mkdirSync(downloadsDir, { recursive: true }); // } // const filePath = path.join(downloadsDir, `document_${req.body.payload.id}.pdf`); // fs.writeFileSync(filePath, Buffer.from(err.body)); // console.error("Error handling esign webhook:", err); res.sendStatus(500) } } async function handleDocumentCompleted(payload = sampleComplete) { //Check if the bodyshop is on image proxy or not try { //Split the external id to get the uploaded user. const [jobid, uploaded_by] = payload.externalId.split("|"); if (!jobid || !uploaded_by) { throw new Error(`Invalid externalId format. Expected "jobid|uploaded_by", got "${payload.externalId}"`); } const { jobs_by_pk } = await client.request(QUERY_META_FOR_ESIG_COMPLETION, { jobid }); const document = await documenso.document.documentDownload({ documentId: payload.id, }); const response = await fetch(document.downloadUrl); const arrayBuffer = await response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); let key = `${jobs_by_pk.bodyshop.id}/${jobs_by_pk.id}/${replaceAccents(document.filename).replace(/[^A-Z0-9]+/gi, "_")}-${new Date().getTime()}.pdf`; if (jobs_by_pk?.bodyshop?.uselocalmediaserver) { //LMS not yet implemented. } else { //S3 Upload const uploadResult = await uploadFileBuffer({ key, buffer, contentType: "application/pdf" }); if (!uploadResult.success) { logger.log(`esig-webhook-s3-upload-error`, "ERROR", "redis", "api", { message: uploadResult.message, stack: uploadResult.stack, jobid: jobid, documentId: payload.id }); } else { logger.log(`esig-webhook-s3-upload-success`, "INFO", "redis", "api", { jobid: jobid, documentId: payload.id, s3Key: key, bucket: uploadResult.bucket }); const auditEntry = await client.request(INSERT_ESIG_AUDIT_TRAIL, { obj: { jobid: jobs_by_pk.id, bodyshopid: jobs_by_pk.bodyshop.id, operation: `Esignature document with title ${payload.title} (ID: ${payload.documentMeta.id}) has been completed.`, useremail: uploaded_by, type: 'esig-complete' } }) //insert the document record with the s3 key and bucket info. await client.request(INSERT_ESIGNATURE_DOCUMENT, { docInput: { jobid: jobs_by_pk.id, uploaded_by: uploaded_by, key, type: "application/pdf", extension: "pdf", bodyshopid: jobs_by_pk.bodyshop.id, size: buffer.length, takenat: new Date().toISOString(), } }) } } } catch (error) { logger.log(`esig-webhook-event-completed-error`, "ERROR", "redis", "api", { message: error.message, stack: error.stack, payload }); } } module.exports = { esignWebhook } const sampleComplete = { "id": 10929, "title": "CASL Title set in JSR", "source": "DOCUMENT", "status": "COMPLETED", "teamId": 742, "userId": 654, "Recipient": [ { "id": 24997, "name": "James Tschetter", "role": "SIGNER", "email": "patrick@imexsystems.ca", "token": "uMom0GwL29NBqMfohGpUE", "signedAt": "2026-02-27T22:11:52.835Z", "expiresAt": "2026-05-28T22:10:48.991Z", "documentId": 10929, "readStatus": "OPENED", "sendStatus": "SENT", "templateId": null, "authOptions": { "accessAuth": [], "actionAuth": [] }, "signingOrder": null, "signingStatus": "SIGNED", "rejectionReason": null, "documentDeletedAt": null, "expirationNotifiedAt": null } ], "createdAt": "2026-02-27T22:10:10.580Z", "deletedAt": null, "updatedAt": "2026-02-27T22:11:57.753Z", "externalId": null, "formValues": null, "recipients": [ { "id": 24997, "name": "James Tschetter", "role": "SIGNER", "email": "patrick@imexsystems.ca", "token": "uMom0GwL29NBqMfohGpUE", "signedAt": "2026-02-27T22:11:52.835Z", "expiresAt": "2026-05-28T22:10:48.991Z", "documentId": 10929, "readStatus": "OPENED", "sendStatus": "SENT", "templateId": null, "authOptions": { "accessAuth": [], "actionAuth": [] }, "signingOrder": null, "signingStatus": "SIGNED", "rejectionReason": null, "documentDeletedAt": null, "expirationNotifiedAt": null } ], "templateId": null, "visibility": "EVERYONE", "authOptions": { "globalAccessAuth": [], "globalActionAuth": [] }, "completedAt": "2026-02-27T22:11:57.752Z", "documentMeta": { "id": "cmm5g3y7u00ecad1sv3ague1w", "message": "CASL Message set in JSR", "subject": "CASL Auth Set in JSR", "language": "en", "timezone": "Etc/UTC", "dateFormat": "yyyy-MM-dd hh:mm a", "redirectUrl": null, "signingOrder": "PARALLEL", "emailSettings": { "documentDeleted": true, "documentPending": true, "recipientSigned": true, "recipientRemoved": true, "documentCompleted": true, "ownerDocumentCompleted": true, "recipientSigningRequest": true }, "distributionMethod": "EMAIL", "drawSignatureEnabled": true, "typedSignatureEnabled": true, "allowDictateNextSigner": false, "uploadSignatureEnabled": true } } // const sampleBody = { // event: "DOCUMENT_COMPLETED", // payload: { // Recipient: [ // { // authOptions: { // accessAuth: [ // ], // actionAuth: [ // ], // }, // documentDeletedAt: null, // documentId: 9827, // email: "patrick@imexsystems.ca", // expired: null, // id: 13311, // name: "Customer Fullname", // readStatus: "OPENED", // rejectionReason: null, // role: "SIGNER", // sendStatus: "SENT", // signedAt: "2026-01-30T18:29:12.648Z", // signingOrder: null, // signingStatus: "SIGNED", // templateId: null, // token: "uiEWIsXUPTbWHd7QedVgt", // }, // ], // authOptions: { // globalAccessAuth: [ // ], // globalActionAuth: [ // ], // }, // completedAt: "2026-01-30T18:29:16.279Z", // createdAt: "2026-01-30T18:28:48.861Z", // deletedAt: null, // documentMeta: { // allowDictateNextSigner: false, // dateFormat: "yyyy-MM-dd hh:mm a", // distributionMethod: "EMAIL", // drawSignatureEnabled: true, // emailSettings: { // documentCompleted: true, // documentDeleted: true, // documentPending: true, // ownerDocumentCompleted: true, // recipientRemoved: false, // recipientSigned: true, // recipientSigningRequest: true, // }, // id: "cml17vfb200qjad1t2spxnc1n", // language: "en", // message: "To perform repairs on your vehicle, we must receive digital authorization. Please review and sign the document to proceed with repairs. ", // redirectUrl: null, // signingOrder: "PARALLEL", // subject: "Repair Authorization for ABC Collision", // timezone: "Etc/UTC", // typedSignatureEnabled: true, // uploadSignatureEnabled: true, // }, // externalId: null, // formValues: null, // id: 9827, // recipients: [ // { // authOptions: { // accessAuth: [ // ], // actionAuth: [ // ], // }, // documentDeletedAt: null, // documentId: 9827, // email: "patrick@imexsystems.ca", // expired: null, // id: 13311, // name: "Customer Fullname", // readStatus: "OPENED", // rejectionReason: null, // role: "SIGNER", // sendStatus: "SENT", // signedAt: "2026-01-30T18:29:12.648Z", // signingOrder: null, // signingStatus: "SIGNED", // templateId: null, // token: "uiEWIsXUPTbWHd7QedVgt", // }, // ], // source: "DOCUMENT", // status: "COMPLETED", // teamId: 742, // templateId: null, // title: "Repair Authorization - 1/30/2026, 6:28:48 PM", // updatedAt: "2026-01-30T18:29:16.280Z", // userId: 654, // visibility: "EVERYONE", // }, // createdAt: "2026-01-30T18:29:18.504Z", // webhookEndpoint: "https://dev.patrickfic.com/esign/webhook", // } function replaceAccents(str) { // Verifies if the String has accents and replace them if (str.search(/[\xC0-\xFF]/g) > -1) { str = str .replace(/[\xC0-\xC5]/g, "A") .replace(/[\xC6]/g, "AE") .replace(/[\xC7]/g, "C") .replace(/[\xC8-\xCB]/g, "E") .replace(/[\xCC-\xCF]/g, "I") .replace(/[\xD0]/g, "D") .replace(/[\xD1]/g, "N") .replace(/[\xD2-\xD6\xD8]/g, "O") .replace(/[\xD9-\xDC]/g, "U") .replace(/[\xDD]/g, "Y") .replace(/[\xDE]/g, "P") .replace(/[\xE0-\xE5]/g, "a") .replace(/[\xE6]/g, "ae") .replace(/[\xE7]/g, "c") .replace(/[\xE8-\xEB]/g, "e") .replace(/[\xEC-\xEF]/g, "i") .replace(/[\xF1]/g, "n") .replace(/[\xF2-\xF6\xF8]/g, "o") .replace(/[\xF9-\xFC]/g, "u") .replace(/[\xFE]/g, "p") .replace(/[\xFD\xFF]/g, "y"); } return str; } `Unexpected Status or Content-Type: Status 200 Content-Type application/pdf\nBody: %PDF-1.7\n%����\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n/Names 74 0 R\n/Dests 75 0 R\n/Info 77 0 R\n/Lang (en-US)\n/Version /1.7\n>>\nendobj\n77 0 obj\n<<\n/Type /Info\n/CreationDate (D:20260227230617Z00'00')\n/Producer \n/ModDate (D:20260227231057Z)…�5[�>�Wu7��V�����Pw�WX�ܮJ'6NWg�vYϳ�����Щr�\n\t+�1��m{휑 �hwb���8��q y�1e�)۱�5m����MVM!�m�[A���{l��\t�hia4��Tm��8��a�e�}� ߫���]MVpяG��֏�jJ<"�A�mO*�P� ������ѧЛ\nendstream\nendobj\n26 0 obj\n<<\n/Length 478/Filter /FlateDecode\n>>\nstream\nx�MSK�9��)��*�O�i��,��o ��kS%�$��hR\rS'�I��~��������T[/�{�k�FC#��֛���;Ӏ�[�⫀m�|Q��\x1b��>� R�����a�E#�pI��._H�ᆫt�k�D3p�I�����W2���oJ0�j���j#��!�$��-������.Ϋ���TI|8D�H��Y��x����1�73%�u�T��Ӑ.rcb�x��Dd6=��Oڏ1 ^�-�...and 252354 more chars`