diff --git a/apps/documentation/pages/developers/webhooks.mdx b/apps/documentation/pages/developers/webhooks.mdx index 024f4e493..3ce2e7ee9 100644 --- a/apps/documentation/pages/developers/webhooks.mdx +++ b/apps/documentation/pages/developers/webhooks.mdx @@ -20,6 +20,7 @@ Documenso supports Webhooks and allows you to subscribe to the following events: - `document.opened` - `document.signed` - `document.completed` +- `document.rejected` ## Create a webhook subscription @@ -36,7 +37,7 @@ Clicking on the "**Create Webhook**" button opens a modal to create a new webhoo To create a new webhook subscription, you need to provide the following information: - Enter the webhook URL that will receive the event payload. -- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`. +- Select the event(s) you want to subscribe to: `document.created`, `document.sent`, `document.opened`, `document.signed`, `document.completed`, `document.rejected`. - Optionally, you can provide a secret key that will be used to sign the payload. This key will be included in the `X-Documenso-Secret` header of the request. ![A screenshot of the Create Webhook modal that shows the URL input field and the event checkboxes](/webhook-images/webhooks-page-create-webhook-modal.webp) @@ -53,45 +54,55 @@ You can edit or delete your webhook subscriptions by clicking the "**Edit**" or The payload sent to the webhook URL contains the following fields: -| Field | Type | Description | -| -------------------------------------------- | --------- | ---------------------------------------------------- | -| `event` | string | The type of event that triggered the webhook. | -| `payload.id` | number | The id of the document. | -| `payload.userId` | number | The id of the user who owns the document. | -| `payload.authOptions` | json? | Authentication options for the document. | -| `payload.formValues` | json? | Form values for the document. | -| `payload.title` | string | The name of the document. | -| `payload.status` | string | The current status of the document. | -| `payload.documentDataId` | string | The identifier for the document data. | -| `payload.createdAt` | datetime | The creation date and time of the document. | -| `payload.updatedAt` | datetime | The last update date and time of the document. | -| `payload.completedAt` | datetime? | The completion date and time of the document. | -| `payload.deletedAt` | datetime? | The deletion date and time of the document. | -| `payload.teamId` | number? | The id of the team. | -| `payload.documentData.id` | string | The id of the document data. | -| `payload.documentData.type` | string | The type of the document data. | -| `payload.documentData.data` | string | The data of the document. | -| `payload.documentData.initialData` | string | The initial data of the document. | -| `payload.Recipient[].id` | number | The id of the recipient. | -| `payload.Recipient[].documentId` | number? | The id the document associated with the recipient. | -| `payload.Recipient[].templateId` | number? | The template identifier for the recipient. | -| `payload.Recipient[].email` | string | The email address of the recipient. | -| `payload.Recipient[].name` | string | The name of the recipient. | -| `payload.Recipient[].token` | string | The token associated with the recipient. | -| `payload.Recipient[].expired` | datetime? | The expiration status of the recipient. | -| `payload.Recipient[].signedAt` | datetime? | The date and time the recipient signed the document. | -| `payload.Recipient[].authOptions.accessAuth` | json? | Access authentication options. | -| `payload.Recipient[].authOptions.actionAuth` | json? | Action authentication options. | -| `payload.Recipient[].role` | string | The role of the recipient. | -| `payload.Recipient[].readStatus` | string | The read status of the document by the recipient. | -| `payload.Recipient[].signingStatus` | string | The signing status of the recipient. | -| `payload.Recipient[].sendStatus` | string | The send status of the document to the recipient. | -| `createdAt` | datetime | The creation date and time of the webhook event. | -| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. | - -## Webhook event payload example - -When an event that you have subscribed to occurs, Documenso will send a POST request to the specified webhook URL with a payload containing information about the event. +| Field | Type | Description | +| -------------------------------------------- | --------- | ----------------------------------------------------- | +| `event` | string | The type of event that triggered the webhook. | +| `payload.id` | number | The id of the document. | +| `payload.externalId` | string? | External identifier for the document. | +| `payload.userId` | number | The id of the user who owns the document. | +| `payload.authOptions` | json? | Authentication options for the document. | +| `payload.formValues` | json? | Form values for the document. | +| `payload.visibility` | string | Document visibility (e.g., EVERYONE). | +| `payload.title` | string | The title of the document. | +| `payload.status` | string | The current status of the document. | +| `payload.documentDataId` | string | The identifier for the document data. | +| `payload.createdAt` | datetime | The creation date and time of the document. | +| `payload.updatedAt` | datetime | The last update date and time of the document. | +| `payload.completedAt` | datetime? | The completion date and time of the document. | +| `payload.deletedAt` | datetime? | The deletion date and time of the document. | +| `payload.teamId` | number? | The id of the team if document belongs to a team. | +| `payload.templateId` | number? | The id of the template if created from template. | +| `payload.source` | string | The source of the document (e.g., DOCUMENT, TEMPLATE) | +| `payload.documentMeta.id` | string | The id of the document metadata. | +| `payload.documentMeta.subject` | string? | The subject of the document. | +| `payload.documentMeta.message` | string? | The message associated with the document. | +| `payload.documentMeta.timezone` | string | The timezone setting for the document. | +| `payload.documentMeta.password` | string? | The password protection if set. | +| `payload.documentMeta.dateFormat` | string | The date format used in the document. | +| `payload.documentMeta.redirectUrl` | string? | The URL to redirect after signing. | +| `payload.documentMeta.signingOrder` | string | The signing order (e.g., PARALLEL, SEQUENTIAL). | +| `payload.documentMeta.typedSignatureEnabled` | boolean | Whether typed signatures are enabled. | +| `payload.documentMeta.language` | string | The language of the document. | +| `payload.documentMeta.distributionMethod` | string | The method of distributing the document. | +| `payload.documentMeta.emailSettings` | json? | Email notification settings. | +| `payload.Recipient[].id` | number | The id of the recipient. | +| `payload.Recipient[].documentId` | number? | The id of the document for this recipient. | +| `payload.Recipient[].templateId` | number? | The template id if from a template. | +| `payload.Recipient[].email` | string | The email address of the recipient. | +| `payload.Recipient[].name` | string | The name of the recipient. | +| `payload.Recipient[].token` | string | The unique token for this recipient. | +| `payload.Recipient[].documentDeletedAt` | datetime? | When the document was deleted for this recipient. | +| `payload.Recipient[].expired` | datetime? | When the recipient's access expired. | +| `payload.Recipient[].signedAt` | datetime? | When the recipient signed the document. | +| `payload.Recipient[].authOptions` | json? | Authentication options for this recipient. | +| `payload.Recipient[].signingOrder` | number? | The order in which this recipient should sign. | +| `payload.Recipient[].rejectionReason` | string? | The reason if the recipient rejected the document. | +| `payload.Recipient[].role` | string | The role of the recipient (e.g., SIGNER, VIEWER). | +| `payload.Recipient[].readStatus` | string | Whether the recipient has read the document. | +| `payload.Recipient[].signingStatus` | string | The signing status of this recipient. | +| `payload.Recipient[].sendStatus` | string | The sending status for this recipient. | +| `createdAt` | datetime | The creation date and time of the webhook event. | +| `webhookEndpoint` | string | The endpoint URL where the webhook is sent. | ## Example payloads @@ -104,9 +115,11 @@ Example payload for the `document.created` event: "event": "DOCUMENT_CREATED", "payload": { "id": 10, + "externalId": null, "userId": 1, "authOptions": null, "formValues": null, + "visibility": "EVERYONE", "title": "documenso.pdf", "status": "DRAFT", "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", @@ -114,7 +127,43 @@ Example payload for the `document.created` event: "updatedAt": "2024-04-22T11:44:43.341Z", "completedAt": null, "deletedAt": null, - "teamId": null + "teamId": null, + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null + }, + "Recipient": [ + { + "id": 52, + "documentId": 10, + "templateId": null, + "email": "signer@documenso.com", + "name": "John Doe", + "token": "vbT8hi3jKQmrFP_LN1WcS", + "documentDeletedAt": null, + "expired": null, + "signedAt": null, + "authOptions": null, + "signingOrder": 1, + "rejectionReason": null, + "role": "SIGNER", + "readStatus": "NOT_OPENED", + "signingStatus": "NOT_SIGNED", + "sendStatus": "NOT_SENT" + } + ] }, "createdAt": "2024-04-22T11:44:44.779Z", "webhookEndpoint": "https://mywebhooksite.com/mywebhook" @@ -128,9 +177,11 @@ Example payload for the `document.sent` event: "event": "DOCUMENT_SENT", "payload": { "id": 10, + "externalId": null, "userId": 1, "authOptions": null, "formValues": null, + "visibility": "EVERYONE", "title": "documenso.pdf", "status": "PENDING", "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", @@ -139,6 +190,22 @@ Example payload for the `document.sent` event: "completedAt": null, "deletedAt": null, "teamId": null, + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null + }, "Recipient": [ { "id": 52, @@ -147,12 +214,12 @@ Example payload for the `document.sent` event: "email": "signer2@documenso.com", "name": "Signer 2", "token": "vbT8hi3jKQmrFP_LN1WcS", + "documentDeletedAt": null, "expired": null, "signedAt": null, - "authOptions": { - "accessAuth": null, - "actionAuth": null - }, + "authOptions": null, + "signingOrder": 1, + "rejectionReason": null, "role": "VIEWER", "readStatus": "NOT_OPENED", "signingStatus": "NOT_SIGNED", @@ -165,12 +232,12 @@ Example payload for the `document.sent` event: "email": "signer1@documenso.com", "name": "Signer 1", "token": "HkrptwS42ZBXdRKj1TyUo", + "documentDeletedAt": null, "expired": null, "signedAt": null, - "authOptions": { - "accessAuth": null, - "actionAuth": null - }, + "authOptions": null, + "signingOrder": 2, + "rejectionReason": null, "role": "SIGNER", "readStatus": "NOT_OPENED", "signingStatus": "NOT_SIGNED", @@ -190,9 +257,11 @@ Example payload for the `document.opened` event: "event": "DOCUMENT_OPENED", "payload": { "id": 10, + "externalId": null, "userId": 1, "authOptions": null, "formValues": null, + "visibility": "EVERYONE", "title": "documenso.pdf", "status": "PENDING", "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", @@ -201,6 +270,22 @@ Example payload for the `document.opened` event: "completedAt": null, "deletedAt": null, "teamId": null, + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null + }, "Recipient": [ { "id": 52, @@ -209,24 +294,18 @@ Example payload for the `document.opened` event: "email": "signer2@documenso.com", "name": "Signer 2", "token": "vbT8hi3jKQmrFP_LN1WcS", + "documentDeletedAt": null, "expired": null, "signedAt": null, - "authOptions": { - "accessAuth": null, - "actionAuth": null - }, + "authOptions": null, + "signingOrder": 1, + "rejectionReason": null, "role": "VIEWER", "readStatus": "OPENED", "signingStatus": "NOT_SIGNED", "sendStatus": "SENT" } - ], - "documentData": { - "id": "hs8qz1ktr9204jn7mg6c5dxy0", - "type": "S3_PATH", - "data": "9753/xzqrshtlpokm/documenso.pdf", - "initialData": "9753/xzqrshtlpokm/documenso.pdf" - } + ] }, "createdAt": "2024-04-22T11:50:26.174Z", "webhookEndpoint": "https://mywebhooksite.com/mywebhook" @@ -240,9 +319,11 @@ Example payload for the `document.signed` event: "event": "DOCUMENT_SIGNED", "payload": { "id": 10, + "externalId": null, "userId": 1, "authOptions": null, "formValues": null, + "visibility": "EVERYONE", "title": "documenso.pdf", "status": "COMPLETED", "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", @@ -251,6 +332,22 @@ Example payload for the `document.signed` event: "completedAt": "2024-04-22T11:52:05.707Z", "deletedAt": null, "teamId": null, + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null + }, "Recipient": [ { "id": 51, @@ -259,12 +356,15 @@ Example payload for the `document.signed` event: "email": "signer1@documenso.com", "name": "Signer 1", "token": "HkrptwS42ZBXdRKj1TyUo", + "documentDeletedAt": null, "expired": null, "signedAt": "2024-04-22T11:52:05.688Z", "authOptions": { "accessAuth": null, "actionAuth": null }, + "signingOrder": 1, + "rejectionReason": null, "role": "SIGNER", "readStatus": "OPENED", "signingStatus": "SIGNED", @@ -284,9 +384,11 @@ Example payload for the `document.completed` event: "event": "DOCUMENT_COMPLETED", "payload": { "id": 10, + "externalId": null, "userId": 1, "authOptions": null, "formValues": null, + "visibility": "EVERYONE", "title": "documenso.pdf", "status": "COMPLETED", "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", @@ -295,11 +397,21 @@ Example payload for the `document.completed` event: "completedAt": "2024-04-22T11:52:05.707Z", "deletedAt": null, "teamId": null, - "documentData": { - "id": "hs8qz1ktr9204jn7mg6c5dxy0", - "type": "S3_PATH", - "data": "bk9p1h7x0s3m/documenso-signed.pdf", - "initialData": "9753/xzqrshtlpokm/documenso.pdf" + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null }, "Recipient": [ { @@ -309,12 +421,15 @@ Example payload for the `document.completed` event: "email": "signer2@documenso.com", "name": "Signer 2", "token": "vbT8hi3jKQmrFP_LN1WcS", + "documentDeletedAt": null, "expired": null, "signedAt": "2024-04-22T11:51:10.055Z", "authOptions": { "accessAuth": null, "actionAuth": null }, + "signingOrder": 1, + "rejectionReason": null, "role": "VIEWER", "readStatus": "OPENED", "signingStatus": "SIGNED", @@ -327,12 +442,15 @@ Example payload for the `document.completed` event: "email": "signer1@documenso.com", "name": "Signer 1", "token": "HkrptwS42ZBXdRKj1TyUo", + "documentDeletedAt": null, "expired": null, "signedAt": "2024-04-22T11:52:05.688Z", "authOptions": { "accessAuth": null, "actionAuth": null }, + "signingOrder": 2, + "rejectionReason": null, "role": "SIGNER", "readStatus": "OPENED", "signingStatus": "SIGNED", @@ -345,6 +463,71 @@ Example payload for the `document.completed` event: } ``` +Example payload for the `document.rejected` event: + +```json +{ + "event": "DOCUMENT_REJECTED", + "payload": { + "id": 10, + "externalId": null, + "userId": 1, + "authOptions": null, + "formValues": null, + "visibility": "EVERYONE", + "title": "documenso.pdf", + "status": "PENDING", + "documentDataId": "hs8qz1ktr9204jn7mg6c5dxy0", + "createdAt": "2024-04-22T11:44:43.341Z", + "updatedAt": "2024-04-22T11:48:07.569Z", + "completedAt": null, + "deletedAt": null, + "teamId": null, + "templateId": null, + "source": "DOCUMENT", + "documentMeta": { + "id": "doc_meta_123", + "subject": "Please sign this document", + "message": "Hello, please review and sign this document.", + "timezone": "UTC", + "password": null, + "dateFormat": "MM/DD/YYYY", + "redirectUrl": null, + "signingOrder": "PARALLEL", + "typedSignatureEnabled": true, + "language": "en", + "distributionMethod": "EMAIL", + "emailSettings": null + }, + "Recipient": [ + { + "id": 52, + "documentId": 10, + "templateId": null, + "email": "signer@documenso.com", + "name": "Signer", + "token": "vbT8hi3jKQmrFP_LN1WcS", + "documentDeletedAt": null, + "expired": null, + "signedAt": "2024-04-22T11:48:07.569Z", + "authOptions": { + "accessAuth": null, + "actionAuth": null + }, + "signingOrder": 1, + "rejectionReason": "I do not agree with the terms", + "role": "SIGNER", + "readStatus": "OPENED", + "signingStatus": "REJECTED", + "sendStatus": "SENT" + } + ] + }, + "createdAt": "2024-04-22T11:48:07.945Z", + "webhookEndpoint": "https://mywebhooksite.com/mywebhook" +} +``` + ## Availability Webhooks are available to individual users and teams. diff --git a/packages/lib/jobs/definitions/internal/seal-document.ts b/packages/lib/jobs/definitions/internal/seal-document.ts index 7586fc40b..1e1de0636 100644 --- a/packages/lib/jobs/definitions/internal/seal-document.ts +++ b/packages/lib/jobs/definitions/internal/seal-document.ts @@ -21,6 +21,7 @@ import { insertFieldInPDF } from '../../../server-only/pdf/insert-field-in-pdf'; import { normalizeSignatureAppearances } from '../../../server-only/pdf/normalize-signature-appearances'; import { triggerWebhook } from '../../../server-only/webhooks/trigger/trigger-webhook'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../../types/document-audit-logs'; +import { ZWebhookDocumentSchema } from '../../../types/webhook-payload'; import { ZRequestMetadataSchema } from '../../../universal/extract-request-metadata'; import { getFile } from '../../../universal/upload/get-file'; import { putPdfFile } from '../../../universal/upload/put-file'; @@ -249,13 +250,14 @@ export const SEAL_DOCUMENT_JOB_DEFINITION = { }, include: { documentData: true, + documentMeta: true, Recipient: true, }, }); await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_COMPLETED, - data: updatedDocument, + data: ZWebhookDocumentSchema.parse(updatedDocument), userId: updatedDocument.userId, teamId: updatedDocument.teamId ?? undefined, }); diff --git a/packages/lib/server-only/document/complete-document-with-token.ts b/packages/lib/server-only/document/complete-document-with-token.ts index 342ec5d56..09e21b964 100644 --- a/packages/lib/server-only/document/complete-document-with-token.ts +++ b/packages/lib/server-only/document/complete-document-with-token.ts @@ -13,6 +13,7 @@ import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { jobs } from '../../jobs/client'; import type { TRecipientActionAuth } from '../../types/document-auth'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import { getIsRecipientsTurnToSign } from '../recipient/get-is-recipient-turn'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; import { sendPendingEmail } from './send-pending-email'; @@ -203,11 +204,19 @@ export const completeDocumentWithToken = async ({ }); } - const updatedDocument = await getDocument({ token, documentId }); + const updatedDocument = await prisma.document.findFirstOrThrow({ + where: { + id: document.id, + }, + include: { + documentMeta: true, + Recipient: true, + }, + }); await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_SIGNED, - data: updatedDocument, + data: ZWebhookDocumentSchema.parse(updatedDocument), userId: updatedDocument.userId, teamId: updatedDocument.teamId ?? undefined, }); diff --git a/packages/lib/server-only/document/create-document.ts b/packages/lib/server-only/document/create-document.ts index a9cfd249e..ddca04bac 100644 --- a/packages/lib/server-only/document/create-document.ts +++ b/packages/lib/server-only/document/create-document.ts @@ -9,6 +9,7 @@ import { DocumentSource, DocumentVisibility, WebhookTriggerEvents } from '@docum import type { Team, TeamGlobalSettings } from '@documenso/prisma/client'; import { TeamMemberRole } from '@documenso/prisma/client'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; export type CreateDocumentOptions = { @@ -135,13 +136,27 @@ export const createDocument = async ({ }), }); + const createdDocument = await tx.document.findFirst({ + where: { + id: document.id, + }, + include: { + documentMeta: true, + Recipient: true, + }, + }); + + if (!createdDocument) { + throw new Error('Document not found'); + } + await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_CREATED, - data: document, + data: ZWebhookDocumentSchema.parse(createdDocument), userId, teamId, }); - return document; + return createdDocument; }); }; diff --git a/packages/lib/server-only/document/reject-document-with-token.ts b/packages/lib/server-only/document/reject-document-with-token.ts index 3ad18a449..33cbc0896 100644 --- a/packages/lib/server-only/document/reject-document-with-token.ts +++ b/packages/lib/server-only/document/reject-document-with-token.ts @@ -3,10 +3,13 @@ import { TRPCError } from '@trpc/server'; import { jobs } from '@documenso/lib/jobs/client'; import { prisma } from '@documenso/prisma'; +import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; +import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; export type RejectDocumentWithTokenOptions = { token: string; @@ -31,6 +34,8 @@ export async function rejectDocumentWithToken({ Document: { include: { User: true, + Recipient: true, + documentMeta: true, }, }, }, @@ -45,8 +50,6 @@ export async function rejectDocumentWithToken({ }); } - // Add the audit log entry before updating the recipient - // Update the recipient status to rejected const [updatedRecipient] = await prisma.$transaction([ prisma.recipient.update({ @@ -88,5 +91,28 @@ export async function rejectDocumentWithToken({ }, }); + // Get the updated document with all recipients + const updatedDocument = await prisma.document.findFirst({ + where: { + id: document.id, + }, + include: { + Recipient: true, + documentMeta: true, + }, + }); + + if (!updatedDocument) { + throw new Error('Document not found after update'); + } + + // Trigger webhook for document rejection + await triggerWebhook({ + event: WebhookTriggerEvents.DOCUMENT_REJECTED, + data: ZWebhookDocumentSchema.parse(updatedDocument), + userId: document.userId, + teamId: document.teamId ?? undefined, + }); + return updatedRecipient; } diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index 5338a3862..a5a1c1079 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -10,6 +10,7 @@ import { DocumentStatus, RecipientRole, SigningStatus } from '@documenso/prisma/ import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { signPdf } from '@documenso/signing'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { getFile } from '../../universal/upload/get-file'; import { putPdfFile } from '../../universal/upload/put-file'; @@ -199,13 +200,14 @@ export const sealDocument = async ({ }, include: { documentData: true, + documentMeta: true, Recipient: true, }, }); await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_COMPLETED, - data: updatedDocument, + data: ZWebhookDocumentSchema.parse(updatedDocument), userId: document.userId, teamId: document.teamId ?? undefined, }); diff --git a/packages/lib/server-only/document/send-document.tsx b/packages/lib/server-only/document/send-document.tsx index 8b6ec9ccb..791d80938 100644 --- a/packages/lib/server-only/document/send-document.tsx +++ b/packages/lib/server-only/document/send-document.tsx @@ -14,6 +14,7 @@ import { WebhookTriggerEvents } from '@documenso/prisma/client'; import { jobs } from '../../jobs/client'; import { extractDerivedDocumentEmailSettings } from '../../types/document-email'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import { getFile } from '../../universal/upload/get-file'; import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; @@ -236,6 +237,7 @@ export const sendDocument = async ({ status: DocumentStatus.PENDING, }, include: { + documentMeta: true, Recipient: true, }, }); @@ -243,7 +245,7 @@ export const sendDocument = async ({ await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_SENT, - data: updatedDocument, + data: ZWebhookDocumentSchema.parse(updatedDocument), userId, teamId, }); diff --git a/packages/lib/server-only/document/viewed-document.ts b/packages/lib/server-only/document/viewed-document.ts index 73ca606cc..7d20c9521 100644 --- a/packages/lib/server-only/document/viewed-document.ts +++ b/packages/lib/server-only/document/viewed-document.ts @@ -6,8 +6,8 @@ import { ReadStatus } from '@documenso/prisma/client'; import { WebhookTriggerEvents } from '@documenso/prisma/client'; import type { TDocumentAccessAuthTypes } from '../../types/document-auth'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import { triggerWebhook } from '../webhooks/trigger/trigger-webhook'; -import { getDocumentAndRecipientByToken } from './get-document-by-token'; export type ViewedDocumentOptions = { token: string; @@ -63,11 +63,23 @@ export const viewedDocument = async ({ }); }); - const document = await getDocumentAndRecipientByToken({ token, requireAccessAuth: false }); + const document = await prisma.document.findFirst({ + where: { + id: documentId, + }, + include: { + documentMeta: true, + Recipient: true, + }, + }); + + if (!document) { + throw new Error('Document not found'); + } await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_OPENED, - data: document, + data: ZWebhookDocumentSchema.parse(document), userId: document.userId, teamId: document.teamId ?? undefined, }); diff --git a/packages/lib/server-only/template/create-document-from-direct-template.ts b/packages/lib/server-only/template/create-document-from-direct-template.ts index 2ff6be164..b7246e5fa 100644 --- a/packages/lib/server-only/template/create-document-from-direct-template.ts +++ b/packages/lib/server-only/template/create-document-from-direct-template.ts @@ -31,6 +31,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import type { TRecipientActionAuthTypes } from '../../types/document-auth'; import { DocumentAccessAuth, ZRecipientAuthOptionsSchema } from '../../types/document-auth'; import { ZFieldMetaSchema } from '../../types/field-meta'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import type { CreateDocumentAuditLogDataResponse } from '../../utils/document-audit-logs'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; @@ -591,7 +592,7 @@ export const createDocumentFromDirectTemplate = async ({ requestMetadata, }); - const updatedDocument = await prisma.document.findFirstOrThrow({ + const createdDocument = await prisma.document.findFirstOrThrow({ where: { id: documentId, }, @@ -603,9 +604,9 @@ export const createDocumentFromDirectTemplate = async ({ await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_SIGNED, - data: updatedDocument, - userId: updatedDocument.userId, - teamId: updatedDocument.teamId ?? undefined, + data: ZWebhookDocumentSchema.parse(createdDocument), + userId: template.userId, + teamId: template.teamId ?? undefined, }); } catch (err) { console.error('[CREATE_DOCUMENT_FROM_DIRECT_TEMPLATE]:', err); diff --git a/packages/lib/server-only/template/create-document-from-template.ts b/packages/lib/server-only/template/create-document-from-template.ts index 6bd09face..cc38b68ca 100644 --- a/packages/lib/server-only/template/create-document-from-template.ts +++ b/packages/lib/server-only/template/create-document-from-template.ts @@ -18,6 +18,7 @@ import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs'; import { ZRecipientAuthOptionsSchema } from '../../types/document-auth'; import type { TDocumentEmailSettings } from '../../types/document-email'; import { ZFieldMetaSchema } from '../../types/field-meta'; +import { ZWebhookDocumentSchema } from '../../types/webhook-payload'; import type { RequestMetadata } from '../../universal/extract-request-metadata'; import { createDocumentAuditLogData } from '../../utils/document-audit-logs'; import { @@ -291,9 +292,23 @@ export const createDocumentFromTemplate = async ({ }), }); + const createdDocument = await tx.document.findFirst({ + where: { + id: document.id, + }, + include: { + documentMeta: true, + Recipient: true, + }, + }); + + if (!createdDocument) { + throw new Error('Document not found'); + } + await triggerWebhook({ event: WebhookTriggerEvents.DOCUMENT_CREATED, - data: document, + data: ZWebhookDocumentSchema.parse(createdDocument), userId, teamId, }); diff --git a/packages/lib/translations/de/common.po b/packages/lib/translations/de/common.po index c278393b6..cd752233f 100644 --- a/packages/lib/translations/de/common.po +++ b/packages/lib/translations/de/common.po @@ -724,7 +724,7 @@ msgid "Document created" msgstr "Dokument erstellt" #: packages/email/templates/document-created-from-direct-template.tsx:32 -#: packages/lib/server-only/template/create-document-from-direct-template.ts:573 +#: packages/lib/server-only/template/create-document-from-direct-template.ts:574 msgid "Document created from direct template" msgstr "Dokument erstellt aus direkter Vorlage" diff --git a/packages/lib/translations/en/common.po b/packages/lib/translations/en/common.po index 6c1469d52..fde25b294 100644 --- a/packages/lib/translations/en/common.po +++ b/packages/lib/translations/en/common.po @@ -719,7 +719,7 @@ msgid "Document created" msgstr "Document created" #: packages/email/templates/document-created-from-direct-template.tsx:32 -#: packages/lib/server-only/template/create-document-from-direct-template.ts:573 +#: packages/lib/server-only/template/create-document-from-direct-template.ts:574 msgid "Document created from direct template" msgstr "Document created from direct template" diff --git a/packages/lib/translations/es/common.po b/packages/lib/translations/es/common.po index 3937f0576..c37203785 100644 --- a/packages/lib/translations/es/common.po +++ b/packages/lib/translations/es/common.po @@ -724,7 +724,7 @@ msgid "Document created" msgstr "Documento creado" #: packages/email/templates/document-created-from-direct-template.tsx:32 -#: packages/lib/server-only/template/create-document-from-direct-template.ts:573 +#: packages/lib/server-only/template/create-document-from-direct-template.ts:574 msgid "Document created from direct template" msgstr "Documento creado a partir de plantilla directa" diff --git a/packages/lib/translations/fr/common.po b/packages/lib/translations/fr/common.po index 244a5a3bb..0f445054d 100644 --- a/packages/lib/translations/fr/common.po +++ b/packages/lib/translations/fr/common.po @@ -724,7 +724,7 @@ msgid "Document created" msgstr "Document créé" #: packages/email/templates/document-created-from-direct-template.tsx:32 -#: packages/lib/server-only/template/create-document-from-direct-template.ts:573 +#: packages/lib/server-only/template/create-document-from-direct-template.ts:574 msgid "Document created from direct template" msgstr "Document créé à partir d'un modèle direct" diff --git a/packages/lib/types/webhook-payload.ts b/packages/lib/types/webhook-payload.ts new file mode 100644 index 000000000..e0626c563 --- /dev/null +++ b/packages/lib/types/webhook-payload.ts @@ -0,0 +1,80 @@ +import { z } from 'zod'; + +import { + DocumentDistributionMethod, + DocumentSigningOrder, + DocumentSource, + DocumentStatus, + DocumentVisibility, + ReadStatus, + RecipientRole, + SendStatus, + SigningStatus, +} from '@documenso/prisma/client'; + +/** + * Schema for recipient data in webhook payloads. + */ +export const ZWebhookRecipientSchema = z.object({ + id: z.number(), + documentId: z.number().nullable(), + templateId: z.number().nullable(), + email: z.string(), + name: z.string(), + token: z.string(), + documentDeletedAt: z.date().nullable(), + expired: z.date().nullable(), + signedAt: z.date().nullable(), + authOptions: z.any().nullable(), + signingOrder: z.number().nullable(), + rejectionReason: z.string().nullable(), + role: z.nativeEnum(RecipientRole), + readStatus: z.nativeEnum(ReadStatus), + signingStatus: z.nativeEnum(SigningStatus), + sendStatus: z.nativeEnum(SendStatus), +}); + +/** + * Schema for document meta in webhook payloads. + */ +export const ZWebhookDocumentMetaSchema = z.object({ + id: z.string(), + subject: z.string().nullable(), + message: z.string().nullable(), + timezone: z.string(), + password: z.string().nullable(), + dateFormat: z.string(), + redirectUrl: z.string().nullable(), + signingOrder: z.nativeEnum(DocumentSigningOrder), + typedSignatureEnabled: z.boolean(), + language: z.string(), + distributionMethod: z.nativeEnum(DocumentDistributionMethod), + emailSettings: z.any().nullable(), +}); + +/** + * Schema for document data in webhook payloads. + */ +export const ZWebhookDocumentSchema = z.object({ + id: z.number(), + externalId: z.string().nullable(), + userId: z.number(), + authOptions: z.any().nullable(), + formValues: z.any().nullable(), + visibility: z.nativeEnum(DocumentVisibility), + title: z.string(), + status: z.nativeEnum(DocumentStatus), + documentDataId: z.string(), + createdAt: z.date(), + updatedAt: z.date(), + completedAt: z.date().nullable(), + deletedAt: z.date().nullable(), + teamId: z.number().nullable(), + templateId: z.number().nullable(), + source: z.nativeEnum(DocumentSource), + documentMeta: ZWebhookDocumentMetaSchema.nullable(), + Recipient: z.array(ZWebhookRecipientSchema), +}); + +export type TWebhookRecipient = z.infer; +export type TWebhookDocument = z.infer; diff --git a/packages/prisma/migrations/20241203223835_add_document_rejected_webhook_enum/migration.sql b/packages/prisma/migrations/20241203223835_add_document_rejected_webhook_enum/migration.sql new file mode 100644 index 000000000..943ed4549 --- /dev/null +++ b/packages/prisma/migrations/20241203223835_add_document_rejected_webhook_enum/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "WebhookTriggerEvents" ADD VALUE 'DOCUMENT_REJECTED'; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 1e58c723f..79c69c120 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -162,6 +162,7 @@ enum WebhookTriggerEvents { DOCUMENT_OPENED DOCUMENT_SIGNED DOCUMENT_COMPLETED + DOCUMENT_REJECTED } model Webhook {