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-here
What 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-Signatureheader - Only 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 attacks - Track Event IDs: Store the
EventIdvalues you've processed to avoid processing the same event twice - Use 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
ZipCodes
Ask 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 seconds - If 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 want - We'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 checkIsActive - If
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 code - Must 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
LastFailureAtandBackoffUntilUtcfields - Fix 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-here
Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /webhooks | Create a webhook |
| GET | /webhooks | List all webhooks |
| GET | /webhooks/{id} | Get webhook details |
| PUT | /webhooks/{id} | Update a webhook |
| DELETE | /webhooks/{id} | Delete a webhook |
| POST | /webhooks/{id}/rotate-secret | Rotate secret key |
| POST | /webhooks/{id}/test-event | 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 seconds - Process webhooks in the background
- Track
EventIdto handle duplicates gracefully - Monitor webhook health in your logs
- Efficiency
- Only subscribe to tables and events you need
- Return
410 Gonefor unwanted webhooks - Use 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"
}