Skip to main content

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": "q3Jx9...Tk8="
}

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 null if 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-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:

  1. Read the complete request body (the JSON payload)
  2. Use your secret key to calculate what the signature should be. The secret is a Base64 string: key the HMAC with its decoded bytes, not with the text itself
  3. Compare it to the X-Signature header
  4. 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, keyed with the Base64-decoded secret
const expectedSignature = crypto
.createHmac('sha256', Buffer.from(secret, 'base64'))
.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, keyed with the Base64-decoded secret
using var hmac = new HMACSHA256(Convert.FromBase64String(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​

  1. Track Event IDs: Store the EventId values you've processed and ignore any you have already handled. This also protects against replays: EventId is inside the signed body, so a replayed notification carries an ID you have already seen. Don't reject notifications by age; retries can arrive hours after the change.
  2. 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": "Zm4Rb...wQ0="
}

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​

  1. Fast Delivery: We send notifications within a minute of a change
  2. Automatic Retries: If your endpoint is temporarily down, we'll keep trying
  3. Retry Schedule: We retry with increasing delays (30 seconds, 1 minute, 2 minutes, 4 minutes, etc.) up to 1 hour between attempts
  4. 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 OK response 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 EventId field to detect and ignore duplicates

Signal When to Stop:

  • Return 410 Gone to 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:

  1. Is the webhook active?
  • Use GET /emws/v1/webhooks/{webhookId} to check IsActive
  • If false, update with {"IsActive": true}
  1. Is your endpoint accessible?
  • Test with the test event endpoint
  • Verify your server is reachable from the internet
  • Check firewall rules and security groups
  1. Are you responding correctly?
  • Must return 200 OK status code
  • Must respond within 10 seconds
  • Check your server logs for errors
  1. Is the webhook in backoff mode?
  • After repeated failures, we temporarily pause deliveries
  • Check the LastFailureAt and BackoffUntilUtc fields
  • Fix the issue and we'll automatically resume

Signature Verification Fails​

Common Mistakes:

  1. Using the parsed JSON instead of raw body
  • ✗ Wrong: JSON.stringify(req.body)
  • ✓ Correct: Use the original request body bytes
  1. Wrong secret encoding
  • Secrets are Base64-encoded strings, and the HMAC key is the decoded bytes
  • Keying the HMAC with the secret's text (for example Encoding.UTF8.GetBytes(secret)) never matches
  1. Comparing during rotation
  • During secret rotation, check both X-Signature and X-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​

MethodEndpointPurpose
POST/webhooksCreate a webhook
GET/webhooksList all webhooks
GET/webhooks/{id}Get webhook details
PUT/webhooks/{id}Update a webhook
DELETE/webhooks/{id}Delete a webhook
POST/webhooks/{id}/rotate-secretRotate secret key
POST/webhooks/{id}/test-eventSend test notification

Error Codes​

CodeMeaning
200Success
201Created successfully
204Deleted successfully
400Invalid request (check your data)
401Missing or invalid API key
403Webhooks not enabled for your account
404Webhook not found
422Request cannot be processed
429Too many requests (rate limited)
5xxServer error (contact support if persistent)

Best Practices Summary​

  1. Security First
  • Always verify signatures
  • Use HTTPS for your callback URLs
  • Keep your API key and secrets secure
  • Ignore an EventId you have already processed, so a replayed notification has no effect
  1. Reliability
  • Respond with 200 OK within 10 seconds
  • Process webhooks in the background
  • Track EventId to handle duplicates gracefully
  • Monitor webhook health in your logs
  1. Efficiency
  • Only subscribe to tables and events you need
  • Return 410 Gone for unwanted webhooks
  • Use custom headers for routing instead of multiple webhooks
  1. 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"
}