Webhooks
This guide walks you through creating and consuming webhooks for the Penneo platform. Webhooks allow you to receive real-time event notifications in your application whenever relevant events occur in your Penneo account.
Before you start
- Public Endpoint: Ensure that your endpoint is publicly accessible. Localhost URLs or private network URLs will not work.
- Test Your Endpoint: We recommend testing with Webhook.site to confirm your endpoint setup before using your own domain. You can also use the
webhook.subscription.test
event type to trigger test events once your subscription is created.
Create a subscription
You can subscribe to one or more event types. Once subscribed, Penneo sends notifications to the specified endpoint whenever those events occur. A complete list of available event types can be found in our API documentation.
POST https://app.penneo.com/webhook/api/v1/subscriptions
Authorization: JWT
X-Auth-Token: <your token>
Accept-charset: utf-8
Accept: application/json
Content-Type: application/json
{
"eventTypes": [
"sign.casefile.completed", "sign.signer.signed"
],
"endpoint": "https://example.com/webhook"
}
{
"customerId": <int>,
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isActive": true,
"secret": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"eventTypes": [
"string"
],
"endpoint": "https://example.com/webhook"
}
Important
Webhook Subscription Limit: A rate limit of 5 active webhook subscriptions per customer is in place. This means each customer account is limited to a maximum of 5 concurrently active webhook subscriptions. If you have a business need for more than 5 active webhook subscriptions, please contact Penneo support by submitting a request here.
Note
If you are not receiving events, verify that the provided endpoint is publicly reachable. Penneo will retry unsuccessful notifications a limited number of times before giving up. See retry policy for details
Consuming webhook events
Webhook events are delivered as HTTPS POST
requests with a Content-Type
of application/json
.
Each request body contains the event payload, and a number of HTTP headers provide metadata to help you identify and validate the request. Your integration should read and parse the headers along with the request body.
Webhook requests must be responded to within 1 second. Connection attempts that take longer than 200ms will fail, and responses that exceed 1 second will be timed out. If your processing exceeds these limits, we recommend buffering the request internally (e.g., enqueuing it) and acknowledging it immediately with a 2xx
response.
Verifying webhook event integrity
Before trusting an incoming event, we strongly recommend validating its authenticity and integrity. Each event delivery includes an X-Event-Signature
header composed of several components. You should perform the following validations, in order:
Parsing the X-Event-Signature
header
X-Event-Signature
headerThe value of the X-Event-Signature
header is a comma-separated list of key-value pairs. Each component is in the form:
key=value
Example:
t=1714567890,h=x-event-id x-event-type,v1=abc123...
You should parse the string into a map and ensure the following required components are present:
t
: the signed timestamph
: the list of signed headersv1
: the signature
Reject the request if the header is malformed or any of these components are missing.
1. Validate the timestamp
The t
component contains the UNIX timestamp of when the signature was generated.
To prevent replay attacks, validate that this timestamp is within an acceptable threshold (e.g., ±5 minutes) from the current time. Reject the request if the difference between the current time and the signed timestamp exceeds the allowed threshold.
2. Validate the signature
Use the values from the parsed X-Event-Signature
components to validate the request:
-
Extract the values of the headers listed in the
h
component (case-insensitive). -
Concatenate the following parts using
.
as a separator:{t}.{h}.{header values joined with '.'}.{raw request body}
-
Compute an HMAC-SHA256 digest of this string using the subscription’s secret as the key.
-
Compare the resulting hex digest to the
v1
value using a constant-time comparison.
Reject the request if any component is missing or if the signature does not match.
The secret token is provided when you create the subscription via the Create Subscription endpoint.
If needed, it can also be retrieved later using the Get Subscriptions endpoint.
Webhook events are delivered at-least-once. You should design your webhook event handlers to be idempotent using theX-Event-Id
header.
Only2xx
responses are considered successful. Any other response code—including3xx
,4xx
, or5xx
—will trigger a retry.
3. Validate the event ID
Each request includes a unique event identifier in the X-Event-Id
header.
To prevent replay of previously seen events, verify that the event ID has not already been processed. We recommend caching event IDs for a short period (e.g., the same duration as the timestamp threshold) to avoid duplicates.
Retry policy
A request can fail for various reasons, most commonly because the target endpoint is not publicly accessible. In addition, a request attempt will fail if it takes longer than 200ms to establish a connection and will timeout after 1 second.
If we fail to send a request to the subscription’s endpoint, we will retry according to the following strategy:
- Up to 5 “fast” retries: Each retry occurs after an interval that starts at 5 seconds and increases by 5 seconds each time (e.g., 5s, 10s, 15s…), until we have attempted 5 fast retries.
- Up to 30 “slow” retries: If it still fails after the fast retries, we switch to slow retries, performing one retry per hour for up to 30 attempts.
After all 35 total retries (5 fast + 30 slow) have been exhausted without success, we stop retrying entirely and the subscription is automatically disabled. An email notification is sent to the user who created the subscription, as well as all administrators of the associated customer.
To resume event delivery, the subscription must be manually updated (e.g., to fix the URL) and re-enabled using the Edit Subscription endpoint.
Specifically for the Sign API
By default, the payload of the webhook contains only a few details: a status code and the numeric ID for either the casefile or the signer.
{
"topic": "casefile",
"eventType": "rejected",
"eventTime": {
"date": "2023-01-01 12:59:59.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"payload": {
"id": <int>,
"status": <int>
}
}
{
"topic": "signer",
"eventType": "finalized",
"eventTime": {
"date": "2023-01-01 12:59:59.000000",
"timezone_type": 3,
"timezone": "UTC"
},
"payload": {
"id": <int>,
"caseFile": {
"id": <int>,
"status": <int>
}
}
}
Important
Webhook notifications are sent for all case files created within the same account, including those created by other team members. Unless you have access to the relevant case file or it’s in a shared folder, you might not have permission to fetch further details for that case file or signer.
If you need a more detailed payload (e.g., title, signer information, etc.), consider creating a super API user. For more information, see Archiving signed documents.
Authorization
Webhooks can use any of the supported Penneo authorization methods, not just JWT as shown in the examples.
Supported subscription event types
Below is an overview of the event types you can subscribe to. You can include one or more of these in a single subscription.
[
“sign.casefile.completed”,
“sign.casefile.expired”,
“sign.casefile.failed”,
“sign.casefile.rejected”,
“sign.signer.requestSent”,
“sign.signer.requestOpened”,
“sign.signer.opened”,
“sign.signer.signed”,
“sign.signer.rejected”,
“sign.signer.reminderSent”,
“sign.signer.undeliverable”,
“sign.signer.requestActivated”,
“sign.signer.finalized”,
“sign.signer.deleted”,
“sign.signer.signedWithImageUploadAndNAP”,
“sign.signer.transientBounce”,
“webhook.subscription.test”
]
Migrating from Topics to Event Types (old vs. new solution)
In the old (now deprecated) webhook solution, you subscribed to topics. In the new solution, you subscribe to event types instead. For example, if you previously subscribed to the “casefile” topic, you can now achieve the same by subscribing to all “casefile” event types.
Event types follow this naming format:
app.topic.event.
For instance the payload for a new subscription would be:{ "eventTypes": [ "sign.casefile.completed", "sign.casefile.expired", "sign.casefile.failed", "sign.casefile.rejected" ], "endpoint": "https://example.com" }
Casefile Events
Event Type | Description |
---|---|
sign.casefile.completed | Everyone has signed and the finalized PDF documents are ready for download. |
sign.casefile.expired | The signing process has expired (e.g., a deadline was reached without all required signatures). |
sign.casefile.failed | An error occurred on our side; this may require contacting support. |
sign.casefile.rejected | One or more signers have rejected the signing request. |
Signer Events
Event Type | Description |
---|---|
sign.signer.requestActivated | The signer is ready to sign; either the case file has been activated, or their signing round has just come up. |
sign.signer.requestSent | The initial signing request email has been sent out. |
sign.signer.reminderSent | A signing reminder email has been sent. |
sign.signer.requestOpened | A signing request email has been opened. |
sign.signer.undeliverable | Penneo cannot send emails to the signer; check the signer’s email address. |
sign.signer.opened | The signer has viewed the signing page. |
sign.signer.rejected | The signer has rejected the signing request. |
sign.signer.signed | The signer has signed. |
sign.signer.signedWithImageUploadAndNAP | The signer has signed using image upload and NAP (if applicable to your organization’s signing flow). |
sign.signer.finalized | The casefile this signer belongs to has been finalized. |
sign.signer.deleted | The signer has been deleted from the casefile. |
sign.signer.transientBounce | The signer’s email temporarily bounced. |
Other Events
Event Type | Description |
---|---|
webhook.subscription.test | A test event you can trigger to ensure your webhook endpoint is set up correctly and can receive notifications. |
Notes and Additional Details
- Multiple Occurrences: Some events, such as
requestSent
,opened
, orsigned
, can occur multiple times if a signer signs in multiple rounds.- Subscribe to Multiple Events: You can combine multiple event types in a single subscription to avoid creating separate subscriptions for each.
If you have any questions or need assistance, please contact our support team.
Updated 9 days ago