Webhooks API
Get instant notifications when your e-manage data changes. This guide will help you set up and manage webhooks to keep your systems in sync with e-manage in real-time.
What are Webhooks?
Webhooks notify your application immediately when something changes in your e-manage database. Instead of constantly checking for updates, e-manage automatically sends you the changes as they happen.
Example: When a project's status changes from "In Progress" to "Completed," we'll send a notification to your system within seconds.
Quick Start
Step 1: Get Your API Key
Contact your e-manage administrator or support team to get your API key. You'll need this to manage your webhook subscriptions.
Important: Keep your API key secure - treat it like a password.
If you have an API key already, please contact support to enable webhooks for your company
Step 2: Create Your First Webhook
Tell e-manage where to send notifications and what changes you want to know about.
API Endpoint: POST <https://emanageone.azure-api.net/emws/v1/webhooks>
What to send:
{
"TableName": "Projects",
"Events": ["Insert", "Update"],
"CallbackUrl": "<https://your-website.com/webhooks>"
}Include your API key in the header:
x-api-key: your-api-key-hereWhat you'll receive back:
{
"Id": "a4eaf4a5-8798-4cd0-8e64-2e84a12a0a61",
"Secret": "sec_2wAd...gK"
}Save the Secret! You'll need it to verify that notifications are actually from e-manage. We only show it once for security reasons.
Step 3: Test Your Webhook
Before going live, send yourself a test notification to make sure everything works.
API Endpoint: POST <https://emanageone.azure-api.net/emws/v1/webhooks/{webhookId}/test-event>
What to send:
{
"Table": "Projects",
"Operation": "Update",
"Keys": { "ProjectID": 123 },
"Current": {
"ProjectID": 123,
"ProjectName": "Test Project",
"Status": "Completed"
}
}This sends a fake notification to your endpoint so you can verify your system receives and processes it correctly.
Understanding Webhook Notifications
What You'll Receive
When data changes in e-manage, we'll send a notification to your CallbackUrl via HTTP POST.
Example Notification:
{
"EventId": "6a1d9f86-6f0c-4c1e-97a0-46ab2f6c4f0d",
"CreatedUtc": "2025-08-20T19:05:13.181Z",
"Table": "Projects",
"Operation": "updated",
"Keys": { "ProjectID": 8324 },
"Current": {
"ProjectID": 8324,
"CompanyID": 5132,
"ProjectName": "Website Redesign",
"Status": "Completed"
},
"ChangedColumns": ["Status"]
}What Each Field Means:
EventId: Unique identifier for this notification (useful for preventing duplicates)
CreatedUtc: When the change happened (UTC timezone)
Table: Which database table changed (e.g., "Projects", "Customers", "Invoices")
Operation: What happened - "created", "updated", or "deleted"
Keys: The unique identifier(s) for the changed record
Current: The current values of the record (will be
nullif deleted)ChangedColumns: Which fields changed (only for updates)
Security Headers
Every notification includes security headers to prove it's from e-manage:
X-Signature: A cryptographic signature (explained below)
X-Event-Id: Matches the EventId in the body
X-Timestamp: When the notification was sent
X-Event-Schema: Version of the notification format (currently "emws.webhook.v1")
Security & Verification
Why Verify Signatures?
Anyone could send fake notifications to your webhook URL. The signature proves the notification is actually from e-manage and hasn't been tampered with.
How to Verify (Simplified)
For Developer Teams:
When you receive a notification:
Read the complete request body (the JSON payload)
Use your secret key to calculate what the signature should be
Compare it to the
X-SignatureheaderOnly process the notification if they match
Code Examples:
Node.js:
const crypto = require('crypto');
function isValidSignature(requestBody, secret, signatureHeader) {
// Remove the "sha256=" prefix from the header
const receivedSignature = signatureHeader.replace('sha256=', '');
// Calculate what the signature should be
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(requestBody)
.digest('hex');
// Securely compare them
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'utf8'),
Buffer.from(receivedSignature, 'utf8')
);
}
// Use it in your webhook handler
app.post('/webhooks', (req, res) => {
const signature = req.headers['x-signature'];
const rawBody = req.rawBody; // Important: use the raw body, not parsed JSON
if (!isValidSignature(rawBody, YOUR_SECRET, signature)) {
return res.status(401).send('Invalid signature');
}
// Signature is valid - process the webhook
const event = JSON.parse(rawBody);
// ... your business logic here ...
res.status(200).send('OK');
});C#:
using System.Security.Cryptography;
using System.Text;
bool IsValidSignature(string requestBody, string secret, string signatureHeader)
{
// Remove the "sha256=" prefix
var parts = signatureHeader?.Split('=', 2);
if (parts?.Length != 2 || parts[0] != "sha256") return false;
var receivedSignature = parts[1];
// Calculate what the signature should be
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(requestBody));
var expectedSignature = BitConverter.ToString(hash)
.Replace("-", "")
.ToLowerInvariant();
// Securely compare them
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expectedSignature),
Encoding.UTF8.GetBytes(receivedSignature)
);
}Additional Security Best Practices
Check the Timestamp: Reject notifications older than 5 minutes (check
X-Timestampheader) to prevent replay attacksTrack Event IDs: Store the
EventIdvalues you've processed to avoid processing the same event twiceUse HTTPS: Always use
<https://> URLs for your CallbackUrl to encrypt notifications in transit
Managing Your Webhooks
All management operations require your API key in the x-api-key header.
List All Your Webhooks
See all webhooks you've created.
API Endpoint: GET <https://emanageone.azure-api.net/emws/v1/webhooks>
Response:
[
{
"Id": "a4eaf4a5-8798-4cd0-8e64-2e84a12a0a61",
"TableName": "Projects",
"Events": ["Insert", "Update"],
"CallbackUrl": "<https://your-website.com/webhooks>",
"IsActive": true,
"CreatedAt": "2025-08-20T19:05:13.181Z"
}
]Get Webhook Details
View details for a specific webhook.
API Endpoint: GET <https://emanageone.azure-api.net/emws/v1/webhooks/{webhookId}>
Update a Webhook
Change which events you want to receive, update your URL, or pause notifications.
API Endpoint: PUT <https://emanageone.azure-api.net/emws/v1/webhooks/{webhookId}>
Examples:
Change which events you receive:
{
"Events": ["Insert", "Update", "Delete"]
}Update your callback URL:
{
"CallbackUrl": "<https://your-new-website.com/webhooks>"
}Pause notifications temporarily:
{
"IsActive": false
}Resume notifications:
{
"IsActive": true
}You can update multiple fields at once by including them all in the request.
Delete a Webhook
Stop receiving notifications permanently.
API Endpoint: DELETE <https://emanageone.azure-api.net/emws/v1/webhooks/{webhookId}>
Response: 204 No Content (success with no body)
Rotate Your Secret Key
If you think your secret has been compromised, generate a new one.
API Endpoint: POST <https://emanageone.azure-api.net/emws/v1/webhooks/{webhookId}/rotate-secret>
What to send:
{
"NewSecret": null
}(Leave null to have e-manage generate a secure secret for you)
Response:
{
"Secret": "sec_new_7cV...0Q"
}Important: For a grace period, we'll send both the old and new signatures in notification headers (X-Signature and X-Signature-Old). This gives you time to update your verification code without missing any notifications. After the grace period ends, only the new signature will be sent.
Customizing Webhook Requests
Adding Custom Headers
You can include custom HTTP headers with every notification (useful for authentication or routing).
When creating a webhook:
{
"TableName": "Projects",
"Events": ["Update"],
"CallbackUrl": "<https://your-website.com/webhooks>",
"HeadersJson": "{\"X-Custom-Auth\":\"abc123\",\"X-Environment\":\"production\"}"
}Every notification will include your custom headers along with the standard ones.
Choosing Which Events to Track
You can subscribe to any combination of these events:
Insert: Notifies when new records are created
Update: Notifies when existing records are modified
Delete: Notifies when records are deleted
Example: Only track new projects:
{
"Events": ["Insert"]
}Example: Track all changes:
{
"Events": ["Insert", "Update", "Delete"]
}Which Tables Can You Track?
You can create webhooks for following tables in your e-manage database:
Companies
CompanyDocuments
CompanyNotes
CompanySalespeople
ContactGroupMembers
ContactGroups
ContactNotes
Contacts
CorporateDivision
Deposits
Employees
EmployeeTime
EventAttendees
EventInvitations
Events
FollowUpRecipients
FollowUps
ImplementationParts
MarketingAnswers
MarketingCampaigns
MarketingQuestions
ProjectContacts
ProjectDocuments
ProjectInstallationInstallers
ProjectInstallationOutSourcing
ProjectInstallationSchedule
ProjectMarketing
ProjectMilestoneAlerts
ProjectMilestones
ProjectNotes
ProjectOrderEntry
ProjectPoitemsAckQty
ProjectPunchListItemDocuments
ProjectPunchListItems
ProjectPurchaseOrderAcknowledgements
ProjectPurchaseOrderItems
ProjectPurchaseOrderItemsAckQtyReceived
ProjectPurchaseOrders
Projects
ProjectSalesPeople
ProjectScope
SalesLeads
SalesTeams
SurveyNames
SystemNoteAlerts
Users
ZipCodesAsk e-manage support to enable any additional tables for your specific use case.
Delivery & Reliability
How We Deliver Webhooks
Fast Delivery: We send notifications within a minute of a change
Automatic Retries: If your endpoint is temporarily down, we'll keep trying
Retry Schedule: We retry with increasing delays (30 seconds, 1 minute, 2 minutes, 4 minutes, etc.) up to 1 hour between attempts
Guaranteed Delivery: We'll keep trying until you respond with success or we determine the endpoint is permanently unreachable
What You Need to Do
Respond Quickly:
Return a
200 OKresponse within 10 secondsIf you need to do heavy processing, accept the webhook first, then process it in the background
Example (Recommended Pattern):
app.post('/webhooks', async (req, res) => {
// 1. Verify signature
if (!isValidSignature(req.body, secret, req.headers['x-signature'])) {
return res.status(401).send('Invalid signature');
}
// 2. Respond quickly
res.status(200).send('OK');
// 3. Process in background (after responding)
processWebhookAsync(req.body).catch(err => {
console.error('Failed to process webhook:', err);
});
});Handle Duplicates:
Due to retries, you might receive the same notification more than once
Use the
EventIdfield to detect and ignore duplicates
Signal When to Stop:
Return
410 Goneto permanently disable a webhook you no longer wantWe'll automatically disable webhooks that consistently fail
Monitoring Your Webhooks
e-manage tracks webhook health automatically:
Failure Count: How many consecutive failed deliveries
Last Failure: When and why the last delivery failed
Backoff Status: If we're temporarily delaying deliveries due to repeated failures
If a webhook fails too many times, we may automatically disable it to prevent wasting resources. You can re-enable it by updating IsActive to true.
Common Use Cases
1. Keep an External System in Sync
Scenario: You have a custom dashboard that displays project data from e-manage.
Solution:
{
"TableName": "Projects",
"Events": ["Insert", "Update", "Delete"],
"CallbackUrl": "<https://dashboard.yourcompany.com/api/sync>"
}When a project changes in e-manage, your dashboard receives a notification and updates immediately.
2. Send Notifications to Your Team
Scenario: Alert your team via Slack when high-priority projects are created.
Solution:
{
"TableName": "Projects",
"Events": ["Insert", "Update"],
"CallbackUrl": "<https://your-middleware.com/project-alerts>"
}Your middleware checks if the project is high-priority and posts to Slack if needed.
3. Trigger Business Workflows
Scenario: Start an approval workflow when an invoice exceeds a certain amount.
Solution:
{
"TableName": "Invoices",
"Events": ["Insert"],
"CallbackUrl": "<https://workflow.yourcompany.com/invoice-approval>"
}Your workflow system receives new invoice notifications and triggers approvals based on your business rules.
4. Audit Trail
Scenario: Maintain a detailed history of all changes to customer records.
Solution:
{
"TableName": "Customers",
"Events": ["Insert", "Update", "Delete"],
"CallbackUrl": "<https://audit.yourcompany.com/log>"
}Store every change in your audit system for compliance and reporting.
Troubleshooting
My Webhook Isn't Receiving Notifications
Check These Common Issues:
Is the webhook active?
Use
GET /emws/v1/webhooks/{webhookId}to checkIsActiveIf
false, update with{"IsActive": true}
Is your endpoint accessible?
Test with the test event endpoint
Verify your server is reachable from the internet
Check firewall rules and security groups
Are you responding correctly?
Must return
200 OKstatus codeMust respond within 10 seconds
Check your server logs for errors
Is the webhook in backoff mode?
After repeated failures, we temporarily pause deliveries
Check the
LastFailureAtandBackoffUntilUtcfieldsFix the issue and we'll automatically resume
Signature Verification Fails
Common Mistakes:
Using the parsed JSON instead of raw body
✗ Wrong:
JSON.stringify(req.body)✓ Correct: Use the original request body bytes
Wrong secret encoding
Secrets are Base64-encoded strings
Make sure to decode if your library requires it
Comparing during rotation
During secret rotation, check both
X-SignatureandX-Signature-Old
Getting 403 Forbidden
Error Message: "Your company does not have webhooks feature enabled"
Solution: Contact e-manage support to enable webhooks for your account.
Getting 404 Not Found
The webhook ID doesn't exist or belongs to a different tenant. Double-check the webhook ID from your list of webhooks.
API Reference Summary
Base URL
<https://emanageone.azure-api.net/emws/v1>Authentication
All requests require the x-api-key header:
x-api-key: your-api-key-hereEndpoints
Method | Endpoint | Purpose |
|---|---|---|
POST |
| Create a webhook |
GET |
| List all webhooks |
GET |
| Get webhook details |
PUT |
| Update a webhook |
DELETE |
| Delete a webhook |
POST |
| Rotate secret key |
POST |
| Send test notification |
Error Codes
Code | Meaning |
|---|---|
200 | Success |
201 | Created successfully |
204 | Deleted successfully |
400 | Invalid request (check your data) |
401 | Missing or invalid API key |
403 | Webhooks not enabled for your account |
404 | Webhook not found |
422 | Request cannot be processed |
429 | Too many requests (rate limited) |
5xx | Server error (contact support if persistent) |
Best Practices Summary
Security First
Always verify signatures
Use HTTPS for your callback URLs
Keep your API key and secrets secure
Check timestamps to prevent replay attacks
Reliability
Respond with
200 OKwithin 10 secondsProcess webhooks in the background
Track
EventIdto handle duplicates gracefullyMonitor webhook health in your logs
Efficiency
Only subscribe to tables and events you need
Return
410 Gonefor unwanted webhooksUse custom headers for routing instead of multiple webhooks
Testing
Always test with the test-event endpoint before going live
Test your signature verification logic
Test failure scenarios (what happens if your endpoint is down?)
Versioning
Current Version: emws.webhook.v1
We may add new optional fields to webhook notifications in the future. Your code should ignore fields it doesn't recognize to maintain forward compatibility.
If we need to make breaking changes, we'll introduce a new version (emws.webhook.v2) and give you plenty of time to migrate.
Quick Reference Card
Creating a Webhook
POST <https://emanageone.azure-api.net/emws/v1/webhooks>
x-api-key: YOUR_KEY
{
"TableName": "Projects",
"Events": ["Insert", "Update"],
"CallbackUrl": "<https://your-site.com/webhooks>"
}Receiving a Webhook
// 1. Verify signature
const isValid = verifySignature(
rawBody,
yourSecret,
req.headers['x-signature']
);
// 2. Respond quickly
res.status(200).send('OK');
// 3. Process async
processInBackground(event);Testing a Webhook
POST <https://emanageone.azure-api.net/emws/v1/webhooks/{id}/test-event>
x-api-key: YOUR_KEY
{
"Table": "Projects",
"Operation": "Update"
}