Like MailHog, but for DocuSign. Mock the DocuSign eSignature API for local development and testing.
3.0K
Like MailHog, but for DocuSign.
DocuHog is a local mock server for the DocuSign eSignature REST API. If you've ever used MailHog to catch outgoing emails during development, DocuHog does the same thing for DocuSign API calls. Point your app at DocuHog instead of DocuSign's sandbox, and every envelope, template, and signing request is captured locally and viewable in a web UI -- no DocuSign developer account, no internet connection, no rate limits.

POST /oauth/token accepts any credentials and returns a valid-looking JWTsent, delivered, completed, declined, or voided with one click or one API callhttp://localhost:8025docker compose up with MailHog includedYou can be up and running in under two minutes. Pick whichever method suits you.
This starts DocuHog alongside MailHog so envelope notification emails are captured too.
git clone https://github.com/dwelch2344/docuhog.git
cd docuhog
docker compose up
docker run -p 8025:8025 welch/docuhog
Multi-arch images (amd64 + arm64) are published to Docker Hub on every release.
Or use the standalone compose file (no MailHog):
docker compose -f docker-compose.standalone.yml up
Requires Node.js 22 or later.
git clone https://github.com/dwelch2344/docuhog.git
cd docuhog
npm install
npm run dev
Open http://localhost:8025 in your browser.
The only change your app needs is to set its DocuSign base URL to http://localhost:8025/restapi. No special credentials are required -- DocuHog's mock OAuth accepts anything.
const docusign = require('docusign-esign');
const apiClient = new docusign.ApiClient();
apiClient.setBasePath('http://localhost:8025/restapi');
apiClient.addDefaultHeader('Authorization', 'Bearer any-token-works');
from docusign_esign import ApiClient
api_client = ApiClient()
api_client.host = 'http://localhost:8025/restapi'
api_client.set_default_header('Authorization', 'Bearer any-token-works')
ApiClient apiClient = new ApiClient("http://localhost:8025/restapi");
apiClient.addDefaultHeader("Authorization", "Bearer any-token-works");
var config = new Configuration(new ApiClient("http://localhost:8025/restapi"));
config.AddDefaultHeader("Authorization", "Bearer any-token-works");
For any HTTP client, just change the base URL:
# Instead of:
https://demo.docusign.net/restapi/v2.1/accounts/{accountId}/envelopes
# Use:
http://localhost:8025/restapi/v2.1/accounts/{accountId}/envelopes
You can also obtain a mock JWT from DocuHog's OAuth endpoint. This is useful if your app's auth flow obtains a token before making API calls:
curl -X POST http://localhost:8025/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=anything"
DocuHog will return a response shaped like a real DocuSign OAuth token. Any credentials are accepted.
DocuHog is configured via environment variables. All have sensible defaults -- you can run it with zero configuration.
| Variable | Default | Description |
|---|---|---|
PORT | 8025 | HTTP server port for the API and web UI |
SMTP_HOST | (none) | SMTP server host for sending notification emails (e.g., mailhog, smtp.mailtrap.io) |
SMTP_PORT | 1025 | SMTP server port |
SMTP_SECURE | false | Use TLS for SMTP connection |
SMTP_USER | (none) | SMTP authentication username |
SMTP_PASS | (none) | SMTP authentication password |
DATA_DIR | ./data | Directory for persisted JSON data (set to /data in Docker) |
LOG_LEVEL | info | Logging level (debug, info, warn, error) |
UI_PORT | same as PORT | Web UI port, if you want it on a separate port |
For detailed configuration examples (SMTP providers, storage options, logging), see docs/CONFIGURATION.md.
DocuHog mocks the following DocuSign eSignature REST API endpoints:
| Method | Endpoint | Description |
|---|---|---|
POST | /oauth/token | Issue mock OAuth tokens (accepts any credentials) |
| Method | Endpoint | Description |
|---|---|---|
POST | /restapi/v2.1/accounts/{accountId}/envelopes | Create an envelope |
GET | /restapi/v2.1/accounts/{accountId}/envelopes | List envelopes |
GET | /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId} | Get envelope details |
PUT | /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId} | Update an envelope |
| Method | Endpoint | Description |
|---|---|---|
POST | /restapi/v2.1/accounts/{accountId}/templates | Create a template |
GET | /restapi/v2.1/accounts/{accountId}/templates | List templates |
GET | /restapi/v2.1/accounts/{accountId}/templates/{templateId} | Get template details |
| Method | Endpoint | Description |
|---|---|---|
POST | /restapi/v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient | Create recipient view (signing URL) |
| Method | Endpoint | Description |
|---|---|---|
GET | /restapi/v2.1/accounts/{accountId} | Get account information |
These endpoints power the DocuHog web UI and are not part of the DocuSign API:
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/health | Health check |
GET | /api/v1/envelopes | List all captured envelopes (for the UI) |
PUT | /api/v1/envelopes/:id/status | Simulate an envelope status change |
For full request/response examples, see docs/API.md.
A Postman collection is included with 36 pre-built requests covering every endpoint.

Open http://localhost:8025 in your browser to see the DocuHog web UI. It shows:

The web UI is served directly by the DocuHog Express server -- there is nothing extra to install or configure.
Because there is no interactive signing ceremony, DocuHog lets you manually move any envelope to any DocuSign status. This is useful for testing how your application responds to each stage of the signing workflow.
Open an envelope in the web UI and look for the Simulate Status Change section. Buttons appear for every valid next status based on the envelope's current state:
| Current status | Available transitions |
|---|---|
created | Sent, Delivered, Completed, Declined, Voided |
sent | Delivered, Completed, Declined, Voided |
delivered | Completed, Declined, Voided |
completed, declined, voided | — (terminal, no further transitions) |
Clicking Declined or Voided prompts for an optional reason string.
PUT /api/v1/envelopes/{envelopeId}/status
Content-Type: application/json
{
"status": "completed"
}
For declined or voided, supply an optional reason:
PUT /api/v1/envelopes/{envelopeId}/status
Content-Type: application/json
{
"status": "declined",
"reason": "Recipient declined to sign"
}
Response (200):
{
"envelopeId": "abc-123",
"status": "completed",
"statusChangedDateTime": "2025-01-15T12:00:00.000Z"
}
Error responses:
| Status | Error code | Meaning |
|---|---|---|
400 | invalid_status | status is missing or not one of sent, delivered, completed, declined, voided |
404 | not_found | No envelope with that ID |
409 | already_terminal | Envelope is already completed, declined, or voided |
Each status change updates the envelope's timestamps (sentDateTime, deliveredDateTime, completedDateTime, voidedDateTime) and each recipient's status accordingly.
When DocuHog "sends" an envelope, it can forward notification emails to an SMTP server. The default docker-compose.yml pairs DocuHog with MailHog so those emails are captured locally too:
docker compose up
This gives you a complete local testing loop: your app sends an envelope through DocuHog, DocuHog fires a notification email to MailHog, and you can inspect both the API payload and the email without anything leaving your machine.

To use a different SMTP provider (Mailtrap, SendGrid, Gmail SMTP, etc.), see docs/CONFIGURATION.md.
DocuHog is a mock server for testing, not a full DocuSign reimplementation. Be aware of the following:
GET .../documents/{documentId} is not implemented.If your tests depend on any of these features, you will need the DocuSign developer sandbox for those specific scenarios.
Contributions are welcome. Please see CONTRIBUTING.md for development setup, coding standards, and the PR process.
Content type
Image
Digest
sha256:81073ebfb…
Size
57.5 MB
Last updated
5 months ago
docker pull welch/docuhog