Gareth Wilson Gareth Wilson

Guide to Autotask Webhooks: Features and Best Practices

Published


Autotask webhooks tell your integration when companies, contacts, assets, tickets and ticket notes change in Autotask PSA, the professional services automation suite that many managed service providers run on. Autotask now comes from Datto, part of Kaseya. If you build a CRM sync, a quoting tool or a middleware connector for Autotask, webhooks replace repeated polling of the API for changes.

This guide covers how Autotask webhooks are created through the API, the callout payload, how the HMAC-SHA1 signature works, and the limits that shape a production integration.

What are Autotask webhooks?

Autotask webhooks exist only in the API. There is no form in the Autotask UI to create one, just a read-only list for administrators. You create a webhook on one entity's webhooks endpoint, such as CompanyWebhooks or TicketWebhooks, and choose which fields trigger it and which are always included in the payload.

When a create, update or delete happens on a record of that entity, Autotask sends a callout: an HTTP POST with a JSON body to your webhook URL on port 443. When the webhook itself is deactivated or deleted, Autotask sends a callout to a second URL you provide, the deactivation URL.

Each callout is signed with a secret key you choose. The X-Hook-Signature header holds sha1= followed by the base64 HMAC-SHA1 of the request body.

Autotask webhook features

FeatureDetails
ConfigurationREST API (v1.0 and later) or SOAP API (v1.6 and later) only
PermissionsAn API-only resource whose security level has Can create WebHooks enabled
EntitiesCompanies, Contacts, Assets (Configuration Items), Tickets, Ticket Notes
Required URLsWebhookUrl for callouts and DeactivationUrl for deactivation notices
TriggersCreate and delete fire for every record. Updates fire when a subscribed field or UDF changes
Signature headerX-Hook-Signature
SchemeHMAC-SHA1, base64, prefixed with sha1=
Signed contentThe request body only
SecretA SecretKey you choose, up to 64 characters
LatencyUsually under a minute, up to five minutes under heavy load
ThroughputAbove 1,500 callouts per rolling hour per entity, further callouts queue
Limit50 active webhooks per entity per site

Autotask doesn't publish an example callout body, its retry schedule, a response timeout or source IPs.

Callouts and the payload

There is no event name. The Action field tells you what happened, and EntityType tells you to which kind of record.

ActionSent whenFields contains
CreateA record is createdAll subscribed fields and UDFs that are non-null
UpdateA subscribed field or UDF changesThe subscribed fields and UDFs that changed
DeleteA record is deletedNothing beyond the base fields
DeactivatedThe webhook is deactivatedNothing beyond the base fields. Guid identifies the webhook

Every callout carries the same base fields:

  • Action: Create, Update, Delete or Deactivated.
  • Guid: a globally unique identifier for the callout.
  • EntityType: the Autotask entity the callout is about.
  • Id: the ID of the record in Autotask.
  • Fields: the fields described above.
  • EventTime: when the webhook fired.
  • SequenceNumber: a number that increases with each callout. Gaps mean items were sent to the error log.
  • PersonID: the Autotask resource ID of the user whose action in the UI triggered the callout.

Autotask doesn't print the exact values of EntityType. Since each webhook belongs to one entity, a receiver with one route or one Hookdeck source per webhook already knows which entity it is handling.

Setting up Autotask webhooks

Allow webhook creation

Webhooks are created by your integration's API-only resource, and its security level must have Can create WebHooks enabled. An administrator sets this on the security level in Autotask's admin settings.

Create the webhook

Choose the secret key first. Autotask allows up to 64 letters, numbers and symbols and recommends at least 10 characters, so generate a long random value and store it as AUTOTASK_WEBHOOK_SECRET. Then create the webhook on your API zone:

curl -X POST "https://webservices[n].autotask.net/atservicesrest/v1.0/CompanyWebhooks" \
  -H "Username: $AUTOTASK_API_USER" \
  -H "Secret: $AUTOTASK_API_SECRET" \
  -H "APIIntegrationcode: $AUTOTASK_INTEGRATION_CODE" \
  -H "Content-Type: application/json" \
  -d '{
    "IsActive": true,
    "Name": "Company sync",
    "WebhookUrl": "https://example.com/webhooks/autotask",
    "DeactivationUrl": "https://example.com/webhooks/autotask/deactivated",
    "IsSubscribedToUpdateEvents": true,
    "SecretKey": "'"$AUTOTASK_WEBHOOK_SECRET"'",
    "SendThresholdExceededNotification": true,
    "NotificationEmailAddress": "ops@example.com"
  }'

The response is the new webhook's itemId. Both WebhookUrl and DeactivationUrl are required.

Add trigger fields

Look up field IDs with GET .../CompanyWebhookFields/entityInformation/fields, then add each one to the webhook:

curl -X POST "https://webservices[n].autotask.net/atservicesrest/v1.0/CompanyWebhooks/4/Fields" \
  -H "Username: $AUTOTASK_API_USER" \
  -H "Secret: $AUTOTASK_API_SECRET" \
  -H "APIIntegrationcode: $AUTOTASK_INTEGRATION_CODE" \
  -H "Content-Type: application/json" \
  -d '{ "FieldID": 7, "IsSubscribedField": true, "IsDisplayAlwaysField": true, "WebhookID": 4 }'

IsSubscribedField makes a change to the field fire the webhook, and IsDisplayAlwaysField includes the field in every payload. Create and update callouts need at least one field. User-defined fields work the same way through <EntityName>WebhookUdfFields.

If your integration writes to Autotask, add its own API resource to <EntityName>WebhookExcludedResources, so its changes don't trigger callouts and loop back into your system.

If you use Hookdeck, create a source with the Autotask source type, paste the same SecretKey into it, and use the Hookdeck source URL as WebhookUrl.

Securing Autotask webhooks

Read X-Hook-Signature, check that it starts with sha1=, and take the rest. Compute the HMAC-SHA1 of the raw request body with the SecretKey, encode it as base64 (keeping the = padding), and compare the two in constant time. Autotask doesn't provide an SDK for this.

const crypto = require('crypto');
const express = require('express');

function verifyAutotaskSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret || !signatureHeader.startsWith('sha1=')) return false;
  const received = Buffer.from(signatureHeader.slice('sha1='.length), 'utf8');
  const expected = Buffer.from(
    crypto.createHmac('sha1', secret).update(rawBody).digest('base64'),
    'utf8'
  );
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/autotask', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyAutotaskSignature(req.body, req.get('x-hook-signature'), process.env.AUTOTASK_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const callout = JSON.parse(req.body.toString('utf8'));
  // Deduplicate on callout.Guid, then route on callout.Action and callout.EntityType.
  res.sendStatus(200);
});

The same check in Python:

import base64
import hashlib
import hmac


def verify_autotask_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret or not signature_header.startswith("sha1="):
        return False
    received = signature_header[len("sha1="):]
    expected = base64.b64encode(hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha1).digest())
    return hmac.compare_digest(received.encode("utf-8"), expected)


# In FastAPI: raw_body = await request.body()
# verify_autotask_signature(raw_body, request.headers.get("x-hook-signature"),
#                           os.environ["AUTOTASK_WEBHOOK_SECRET"])

Note that the hash is SHA-1, not SHA-256, and the digest is base64, not hex. Autotask suggests checking your understanding with an online HMAC generator set to SHA-1 with base64 output. There is no timestamp in the signature, so there is no replay window to check.

Make Autotask webhooks production-ready. Hookdeck Event Gateway verifies the X-Hook-Signature header on every callout, stores each company, contact and ticket change, and delivers it to your integration at a rate it can handle.

Autotask webhook limitations and pain points

No published retry schedule, and deactivation at the end

The Problem: An outage on your side ends with the webhook switched off.

Why It Happens: Autotask retries a failed callout behind the scenes, but doesn't say how often or for how long. If the callout keeps failing, the webhook is deactivated and Autotask calls your deactivation URL.

Workarounds:

  • Handle the deactivation URL: alert on it, because it means callouts have stopped.
  • After an outage, reactivate the webhook through the API and reconcile changed records by querying for recent updates.

How Hookdeck Can Help: Hookdeck accepts Autotask's callouts as soon as they arrive, so failures on your side don't count against the webhook. Callouts wait in Hookdeck and are delivered with retries you configure.

SHA-1 and base64 trip up copied code

The Problem: Verification fails on every callout even with the right key.

Why It Happens: Most webhook samples use SHA-256 and hex. Autotask uses HMAC-SHA1 with a base64 digest and a sha1= prefix, and stripping the base64 = padding also breaks the comparison.

Workarounds:

  • Use sha1 and base64 exactly as shown above.
  • Compare the base64 strings as they are, padding included.

How Hookdeck Can Help: Hookdeck's Autotask source type applies the right algorithm and encoding, so your service only needs to trust Hookdeck.

Throughput limits during bulk changes

The Problem: After an import or bulk update, callouts arrive many minutes late.

Why It Happens: Above 1,500 callouts per rolling hour for an entity, Autotask queues further callouts until the rate drops below the threshold.

Workarounds:

  • Set SendThresholdExceededNotification to true and a NotificationEmailAddress, so you know when the threshold is crossed.
  • Subscribe only to the fields you need, so routine edits don't use up the hourly allowance.

How Hookdeck Can Help: Hookdeck records when each callout arrived, which makes a threshold delay easy to see, and it can deliver bursts to your service at a steady rate.

Callout loops

The Problem: Your integration updates a ticket, which fires a callout, which makes your integration update the ticket again.

Why It Happens: Changes made through the API trigger webhooks like any other change.

Workarounds:

  • Add your integration's API resource to <EntityName>WebhookExcludedResources.
  • Ignore callouts whose PersonID is your integration's resource.

How Hookdeck Can Help: A Hookdeck filter on PersonID can drop callouts caused by your own integration before they reach it.

Best practices

Verify against the raw body

The HMAC covers the body exactly as sent. Use express.raw({ type: '*/*' }) in Express, await request.text() in Next.js and await request.body() in FastAPI, and parse only after the signature check passes.

Deduplicate on Guid

Guid identifies a callout uniquely across Autotask databases. Record it before processing and skip repeats. See our guide to webhook idempotency.

Use SequenceNumber for ordering

SequenceNumber always increases. Store the last number you processed per record and ignore older callouts. Gaps are expected, because some callouts go to Autotask's error log.

Include the fields you route on

Set IsDisplayAlwaysField on the fields your handler needs every time, such as external IDs, so you don't have to call the API for each callout.

Acknowledge fast and process asynchronously

Verify, store the callout, return 200, and do the sync work afterwards. See why to process webhooks asynchronously.

Conclusion

Autotask webhooks are created per entity through the API and signed with X-Hook-Signature, a sha1=-prefixed base64 HMAC-SHA1 of the body keyed with the SecretKey you chose. Verify with SHA-1 and base64, deduplicate on Guid, order on SequenceNumber, exclude your own API resource to avoid loops, and watch the deactivation URL.

Hookdeck Event Gateway verifies every Autotask callout and holds it until your integration has processed it, so an outage on your side doesn't end with a deactivated webhook.

Get started with Hookdeck for free and handle Autotask webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.