Retrieves the current status of an asynchronous job by providing the job UUID and payload hash. This endpoint is used to poll for job completion status after submitting a case file creation request. The endpoint is rate-limited to 20 requests per minute per submission. No authentication is required as the UUID and payload hash provide the necessary security.
Open API 3.0 Specs
The Open API 3.0 Specs are available on the following urls:
Job Status Check Documentation
This document describes how to check the status of asynchronous case file creation jobs using the Penneo API.
Table of Contents
- Overview
- Endpoint
- Authentication
- Request Structure
- Response Structure
- Job Status Values
- Examples
- Error Handling
- Usage Guide
Overview
When you create a case file using the Penneo API, the operation is processed asynchronously. The API immediately returns a job UUID and payload hash, which you can use to poll for the job's completion status.
Key Features:
- No authentication required (UUID + payload hash provide security)
- Rate limited to 20 requests per minute per submission
- Real-time job status tracking
- Detailed result information when completed
Authentication
No authentication required for this endpoint. Security is provided through:
- The unique job UUID (returned when job is created)
- The payload hash
These two values together ensure only authorized users can check job status.
Request Structure
Request Body
The request body must be a JSON object with two required fields.
Required Properties
| Property | Type | Format | Description | Example |
|---|---|---|---|---|
uuid | string | UUID v4 | The unique identifier of the job to check | "123e4567-e89b-12d3-a456-426614174000" |
payloadHash | string | SHA-256 hash | The hash of the original payload used for verification | "3a7bd3e2360a3d485f2a5f4d8e6f1e8b9c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f" |
Request Example
{
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"payloadHash": "3a7bd3e2360a3d485f2a5f4d8e6f1e8b9c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f"
}Getting UUID and Payload Hash
When you create a case file, the API returns these values in the response:
{
"message": "Job created",
"status": "handled",
"jobs": [
{
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"payloadHash": "3a7bd3e2360a3d485f2a5f4d8e6f1e8b9c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f",
...
}
]
}💡 Tip: Store both the uuid and payloadHash immediately after creating a job so you can check its status later.
Response Structure
Success Response (200 OK)
When the job is found, you receive a complete job object with all details.
Response Properties
| Property | Type | Format | Required | Description |
|---|---|---|---|---|
uuid | string | UUID | Yes | Unique identifier for the job |
jobStatus | string | enum | Yes | Current status of the job. See Job Status Values |
payload | object | JSON | Yes | The original payload submitted with the job |
payloadHash | string | SHA-256 | Yes | SHA-256 hash of the payload |
result | object | null | JSON | Yes | The result of job execution (populated when jobStatus is "completed") |
createdAt | string | ISO 8601 | Yes | Date and time when the job was created |
updatedAt | string | ISO 8601 | Yes | Date and time when the job was last updated |
errorMessage | string | null | - | Yes | Error message if job failed (only present when jobStatus is "failed") |
Automatic Retry of Failed Jobs
In our response, we return the
retriesandmaxRetriesproperties, which are defaulted to 0. Currently, automatic retry of failed jobs is not enabled as it is part of our experimental features. We will update this information once the mechanism is available and the documentation is complete.
Success Response Example
{
"uuid": "7e47477c-e543-43ad-90a9-50e6abe9abdc",
"type": "etl-queue",
"payloadHash": "1d3644ac95ec37b073d9d8be2dca5da66f36533397a88c0462dcc219d049db70",
"jobStatus": "completed",
"errorMessage": null,
"retries": 0,
"maxRetries": 0,
"createdAt": "2025-11-11T10:34:46.140Z",
"updatedAt": "2025-11-11T10:34:51.574Z",
"result": {
"data": {
"caseFile": {
"id": 1234
},
"signingLinks": [
{
"name": "John Doe",
"role": "signer",
"signerId": 12345,
"signOrder": [
0
],
"signingLink": "https://app.penneo.com/signing/12345-ABCDE-56789-FGHIJ-KLMNO-PQRS"
}
]
},
"errors": null,
"success": true
}
}Error Response (404 Not Found)
When the job is not found or the payload hash doesn't match.
Error Properties
| Property | Type | Description | Example |
|---|---|---|---|
statusCode | number | HTTP status code | 404 |
message | string | Human-readable error message | "Job not found" |
Error Response Example
{
"statusCode": 404,
"message": "Job not found"
}Common reasons for 404:
- Job UUID doesn't exist
- Payload hash doesn't match the job
- Job has expired (retention policy applied)
- Job has been deleted
Job Status Values
The jobStatus field indicates the current state of the job.
| Status | Description | Next Action |
|---|---|---|
pending | Job is waiting to be processed | Continue polling |
processing | Job is currently being executed | Continue polling |
completed | Job finished successfully | Check result field for outcome |
failed | Job execution failed | Check errorMessage field for details |
Status Workflow
┌─────────┐
│ pending │ → Job created, waiting in queue
└────┬────┘
↓
┌────────────┐
│ processing │ → Job is being executed
└─────┬──────┘
↓
┌──┴──────────────────────────┐
│ │
↓ ↓
┌──────────┐ ┌────────┐
│completed │ → get CF defails │ failed │ → Check errorMessage
└──────────┘ └────────┘
Examples
Checking Job Status (Completed)
Request:
const url = 'https://sandbox.penneo.com/send/api/v1/queue/public/status';
const data = {
uuid: '123e4567-e89b-12d3-a456-426614174000',
payloadHash: '3a7bd3e2360a3d485f2a5f4d8e6f1e8b9c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f'
};
async function checkQueueStatus() {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const responseBody = await response.text();
if (!response.ok) {
console.error('Error status:', response.status);
console.error('Error body:', responseBody);
return;
}
// If response is JSON, parse it
const result = JSON.parse(responseBody);
console.log('Queue Status:', result);
} catch (error) {
console.error('Fetch failed:', error.message);
}
}
checkQueueStatus();
Response:
{
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"type": "etl-queue",
"payloadHash": "3a7bd3e2360a3d485f2a5f4d8e6f1e8b9c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f",
"jobStatus": "completed",
"errorMessage": null,
"retries": 0,
"maxRetries": 5,
"createdAt": "2025-11-14T12:12:38.413Z",
"updatedAt": "2025-11-14T12:12:41.957Z",
"result": {
"data": {
"caseFile": {
"id": 1234
},
"signingLinks": [
{
"name": "Test User",
"role": "Signer",
"signerId": 2044563,
"signOrder": [
0
],
"signingLink": "https://sandbox.penneo.com/signing/ABCDE-12345-FGHIJ-KLMNO-56789-PQRST"
}
]
},
"errors": null,
"success": true
}
}Error Handling
HTTP Status Codes
| Status Code | Description | Action |
|---|---|---|
| 200 OK | Job found and status returned | Process the job status |
| 404 Not Found | Job not found or hash mismatch | Verify UUID and payload hash |
| 429 Too Many Requests | Rate limit exceeded (>20 req/min) | Wait before retrying |
| 500 Internal Server Error | Server error | Retry after a delay |
Usage Guide
Polling Strategy
Implement a polling strategy to check job status without overwhelming the API.
Recommended Polling Intervals
| Job Status | Polling Interval | Max Duration |
|---|---|---|
pending | 5 seconds | 10 minutes, but we expect much faster |
processing | 3-5 seconds | 5 minutes, but we expect much faster |
completed | Stop polling | - |
failed | Stop polling | - |
Best Practices
1. Store Job Details Immediately
After creating a case file, immediately store the uuid and payloadHash as they will not be retrievable later.
2. Set Reasonable Timeouts and respect the Rate Limits
Don't poll indefinitely. Set a reasonable timeout:
- Recommended timeout: at least 10 minutes until you see a status change but usually you will have the CaseFile created or failed in a few seconds depending on the number of signers and documents.
- Maximum attempts: 20 polls / minute / CaseFile. After exhausting the limit, 429 HTTP responses will be returned.
- Polling interval: recommend minimum 3 seconds to fit the maximum attempts but we count the number of attempts per minute, so it is at your discretion if you want to exponentially increased the interval to fit the 20 requests / minute.
Security Considerations
Protect Credentials
The uuid and payloadHash together provide access to job status:
- Store them securely
- Don't share payload hashes/uuid publicly
Integration Workflow
Complete Case File Creation + Status Check Flow
1. Create Case File
↓
2. Receive Job UUID + Payload Hash
↓
3. Store UUID + Hash in Database/Your own storage
↓
4. Start Polling (every 3-5 seconds)
↓
5. Check Job Status
↓
├─→ pending/processing → Continue Polling
├─→ completed → Process Result
└─→ failed → Handle Error
flowchart TD
A[Start: createAndMonitorCaseFile] --> B[Step 1: Create Case File]
B --> C{Is createResponse.status 'handled'?}
C -- No --> D[Throw Error: Failed to create case file]
C -- Yes --> E[Step 2: Store job details in database]
E --> F[Step 3 Poll job status call public/status every 3s]
%% Polling loop
F --> G{completedJob.jobStatus}
G -- pending/processing --> H[Wait 3 seconds then poll again]
H --> F
G -- completed --> I[Update job as 'completed' in database]
I --> J[Return completedJob.result]
G -- failed --> K[Update job as 'failed' in database]
K --> L[Return failedJob.result]