Gareth Wilson Gareth Wilson

Guide to Acumatica Webhooks: Features and Best Practices

Published


Acumatica webhooks, which Acumatica calls push notifications, tell your systems when ERP data changes. A stock item's status changes, a sales order is added, a customer is updated: if the change shows up in a query you are monitoring, Acumatica sends the changed rows to your endpoint, so an e-commerce site, warehouse system or data pipeline can stay in sync without polling the REST API.

This guide covers how push notifications are configured and authenticated, how to read a notification's rows, how Acumatica retries, and the practices that make an Acumatica receiver dependable. Acumatica is also sold in Australia and New Zealand as MYOB Acumatica, which shares the same push notification framework.

What are Acumatica webhooks?

Acumatica push notifications are built on queries. On the Push Notifications form (SM302000), you choose a destination, then pick one or more generic inquiries (or built-in query definitions) to monitor. Whenever a data transaction changes the results of one of those queries, Acumatica produces a notification describing the change. With a destination of type Webhook, that notification is an HTTP POST with a JSON body.

There is no list of event types. A notification says "the results of this query changed", names the query in Query, and lists the affected rows in Inserted and Deleted. What counts as an event is decided by the inquiries you build.

Acumatica does not sign push notifications. The Webhook destination has Header Name and Header Value boxes, which Acumatica's documentation says are there "for security reasons". The header you set is added to every request, and your receiver compares it with the value you stored.

Acumatica webhook features

FeatureDetails
ConfigurationPush Notifications form (SM302000), destination type Webhook
TriggerA change in the results of a monitored generic inquiry or built-in query
Method and formatPOST, JSON
AuthenticationOne custom header from Header Name and Header Value
Reserved headersDo not use Accept or Content-Type; the system sets them
Signature and timestampNone
Event type fieldNone. Query names the inquiry
Deduplication keyId, the data transaction that caused the notification
OrderingTimeStamp, a long value for the transaction time
RetriesResent when your endpoint returns an error, at most five attempts
Failed notificationsListed on Process Push Notifications (SM502000) and can be resent for two days
Source IPsNot published. Many instances are self-hosted

Other destination types exist, including a Microsoft message queue and a SignalR hub, but only the Webhook type sends HTTP requests to your endpoint.

The notification payload

Here is Acumatica's example for a stock items inquiry, sent when item AACOMPUT01 changes from Active to Inactive:

{
  "Inserted": [
    {
      "InventoryID": "AACOMPUT01",
      "StockItem": true,
      "ItemStatus": "Inactive",
      "InventoryItem_lastModifiedDateTime": "2024-05-05T15:16:23.1"
    }
  ],
  "Deleted": [
    {
      "InventoryID": "AACOMPUT01",
      "StockItem": true,
      "ItemStatus": "Active",
      "InventoryItem_lastModifiedDateTime": "2024-05-05T15:16:23.103"
    }
  ],
  "Query": "Stock Items: Last Modified Date",
  "CompanyId": "MyTenant",
  "Id": "1af4d140-5321-41f2-a2ec-50b67f577c6c",
  "TimeStamp": 636295833829493672,
  "AdditionalInfo": {}
}

Each row's keys are the inquiry's columns. You work out what happened to a row from where it appears:

ChangeWhere the row appears
InsertedInserted only
DeletedDeleted only
UpdatedOld values in Deleted, new values in Inserted

If you track specific fields rather than all of them, fields that did not change come back as null in the row. If you use multilingual user input and a multilingual field changes, the same value can appear in both Inserted and Deleted. AdditionalInfo can carry extra system or custom information you configure.

Inspect Acumatica webhooks as they arrive. Point a Webhook destination at a Hookdeck Inspect URL and change a row your inquiry returns to see the exact notification, with no account or setup required.

Setting up Acumatica webhooks

Pick a header name and a long random value first, for example Authorization and Bearer <token>, with the token from openssl rand -hex 32.

  1. Open the Push Notifications form (SM302000).
  2. Enter a Destination Name and select Webhook as the Destination Type.
  3. Enter your HTTPS endpoint in Address.
  4. Enter the Header Name and Header Value.
  5. Select Active.
  6. On the Generic Inquiries tab, add the inquiries to monitor. Select Track All Fields, or list the fields you care about so that unrelated changes do not produce notifications.
  7. Save.

To route through Hookdeck, create a Hookdeck source with the Acumatica type and use its URL as the Address. Enter the same header name in the source's API Key Header field, and the exact value, including any Bearer prefix, in its API Key field. Hookdeck looks the header up by name and compares the whole value. It accepts POST.

Securing Acumatica webhooks

Read the header you named, compare its whole value with the one you stored in constant time, and only then parse the body. Header names are case-insensitive, so look the name up in lower case in Node.

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

const HEADER = (process.env.ACUMATICA_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const SECRET = process.env.ACUMATICA_WEBHOOK_SECRET; // the exact Header Value

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/acumatica', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.headers[HEADER];
  if (!SECRET || typeof received !== 'string' || !safeEqual(received, SECRET)) {
    return res.sendStatus(401);
  }

  let notification;
  try {
    notification = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Store it keyed on notification.Id, return 200, then route on notification.Query.
  res.sendStatus(200);
});

The same check in Python:

import hmac
import os


def verify_acumatica_request(headers) -> bool:
    secret = os.environ.get("ACUMATICA_WEBHOOK_SECRET")
    if not secret:
        return False  # never accept when unconfigured
    name = os.environ.get("ACUMATICA_WEBHOOK_HEADER", "Authorization")
    received = headers.get(name)  # case-insensitive in Starlette and Flask
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), secret.encode("utf-8"))

A static header authenticates the sender, not the body. It cannot detect a modified notification, and with no signature or timestamp a captured request can be replayed and still pass. Use HTTPS, a long random value, and change the value on the destination and in your environment together if it might have leaked. A destination with no header carries nothing to check, so always set one.

Make Acumatica webhooks production-ready. Hookdeck Event Gateway checks your header on every push notification, answers Acumatica straight away so its five attempts are never used up, and queues each change durably for your service.

Acumatica webhook limitations and pain points

Errors after processing create duplicates

The Problem: Your system ends up with the same change applied twice.

Why It Happens: Acumatica resends a notification whenever your endpoint replies with an error. If your code applied the change and then failed, for example while writing a log or calling another service, Acumatica sends the same notification again. Acumatica's documentation warns about exactly this.

Workarounds:

  • Store the notification's Id before you process it, and skip any Id you have already seen.
  • Return 200 as soon as the notification is stored, and do the work afterwards.

How Hookdeck Can Help: Hookdeck answers Acumatica as soon as each notification arrives, so failures in your own code never cause Acumatica to resend. Deduplication on Id can drop repeats before they reach your service.

Five attempts, then manual recovery

The Problem: A short outage leaves changes stranded in Acumatica, and someone has to resend them by hand.

Why It Happens: Acumatica makes at most five attempts to deliver a notification. After that it is listed on the Process Push Notifications form (SM502000), where it can be resent for two days before it is removed. The interval between attempts is not documented. Notifications that failed before they were sent, for example because Acumatica could not read the data, do not appear on that form.

Workarounds:

  • Check the Process Push Notifications form after any outage, and resend from there.
  • Reconcile important records against Acumatica's REST API on a schedule.

How Hookdeck Can Help: Hookdeck stores every notification it accepts and delivers it to your service with retries on your schedule, so an outage on your side no longer depends on Acumatica's five attempts or its two-day window. You can replay any notification after a fix.

No event types to route on

The Problem: Every notification looks alike, and your handler has to work out what changed.

Why It Happens: Acumatica sends query results, not events. The only routing key is Query, the name of the inquiry, and the kind of change comes from the Inserted and Deleted arrays.

Workarounds:

  • Give each integration its own inquiry with a clear, stable name, and route on Query.
  • Treat a row in both arrays as an update, and compare the two to see which fields changed.
  • Keep inquiry names stable, because renaming one changes Query and breaks routing.

How Hookdeck Can Help: Hookdeck filters can route notifications by Query to different destinations, so each service only receives the inquiries it handles.

Notification floods from broad inquiries

The Problem: A bulk import or a mass update sends thousands of notifications at once.

Why It Happens: Every data transaction that changes the monitored results produces a notification. Track All Fields on a broad inquiry means almost any edit counts.

Workarounds:

  • Track only the fields you need, and narrow the inquiry with filters.
  • Make the receiving endpoint as thin as possible.

How Hookdeck Can Help: Hookdeck absorbs the burst and delivers at a max delivery rate your service can handle.

Best practices

Deduplicate on Id

Id identifies the data transaction that caused the notification, and Acumatica's documentation suggests using it to omit duplicates. Store it before processing. See our guide to webhook idempotency.

Order by TimeStamp

Acumatica includes TimeStamp so the receiver can work out the order of notifications, which matters once resends are involved. Use it to decide which change is newer, and ignore older ones for the same record.

Treat null as "not changed" when tracking fields

With specific tracked fields, a null in a row can mean "this field did not change", not "this field is empty". Merge rows into your stored record rather than overwriting it.

Acknowledge fast and process asynchronously

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

Conclusion

Acumatica sends push notifications to a Webhook destination as an unsigned JSON POST listing the rows that entered and left a monitored query, with one custom header you choose as the only credential. Compare that header's whole value in constant time over HTTPS, route on Query, deduplicate on Id, order by TimeStamp, and never return an error for a notification you have already stored.

Hookdeck Event Gateway checks your header, answers Acumatica immediately, and keeps every notification until your service has processed it, so a failure on your side does not leave changes waiting on the Process Push Notifications form.

Get started with Hookdeck for free and handle Acumatica 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.