Webhook subscriptions

Subscriptions that push events to your server instead of you polling for them. A subscription belongs to the token that created it and only ever delivers events that token has the scope to read. The envelope, signature verification and the retry schedule are in Webhooks - read that first; this page is the subscription object and the event catalogue.

The object#

FieldTypeDescription
idread-onlyintegerNolaPro id.
urlstring(500)HTTPS endpoint to POST to. Plain HTTP is refused - the payload carries business data and the signature is worthless over a channel anyone can read.
eventsarray of stringEvent types to receive. * is not accepted: naming them is what stops a new event type silently appearing in your handler.
descriptionstring(100)What this subscription is for. Shown on the token screen.
pausedbooleanPause delivery without cancelling the subscription. A paused subscription drops events rather than queueing them - it is a tap, not a buffer. Deliberately not called active: D23 reserves that word, and cancel already means something else here.
failcountread-onlyintegerConsecutive failed deliveries. Resets on the first success.
lastdeliverydateread-onlystringLast successful delivery.
disableddateread-onlystringSet when we gave up. See the disabling rule in Webhooks.
cancelread-onlybooleanCancelled.
enabledrequest-onlybooleanWrite-only. Send true to revive a subscription we disabled after six failed deliveries: it clears disableddate AND resets failcount, so it gets a fresh six. Clearing the date alone would disable it again on the next failure. Not the same as paused, which is your own on/off switch and never resets anything.

Endpoints#

GET/webhooks 200

List subscriptions.

Requires scope setup:read.

Parameters
NameInTypeNotes
cancelquerystringDefaults to false. Pass true or any.
curl \
  'https://acme.nolapro.com/!/api/v2/webhooks' \
  -H 'Authorization: Bearer $NP_TOKEN'
POST/webhooks 201400422

Create a subscription.

The response is the only time secret is returned. Store it before you acknowledge the call.

Requires scope setup:write.

Parameters
NameInTypeNotes
Body
FieldTypeDescription
idread-onlyintegerNolaPro id.
urlstring(500)HTTPS endpoint to POST to. Plain HTTP is refused - the payload carries business data and the signature is worthless over a channel anyone can read.
eventsarray of stringEvent types to receive. * is not accepted: naming them is what stops a new event type silently appearing in your handler.
descriptionstring(100)What this subscription is for. Shown on the token screen.
pausedbooleanPause delivery without cancelling the subscription. A paused subscription drops events rather than queueing them - it is a tap, not a buffer. Deliberately not called active: D23 reserves that word, and cancel already means something else here.
failcountread-onlyintegerConsecutive failed deliveries. Resets on the first success.
lastdeliverydateread-onlystringLast successful delivery.
disableddateread-onlystringSet when we gave up. See the disabling rule in Webhooks.
cancelread-onlybooleanCancelled.
enabledrequest-onlybooleanWrite-only. Send true to revive a subscription we disabled after six failed deliveries: it clears disableddate AND resets failcount, so it gets a fresh six. Clearing the date alone would disable it again on the next failure. Not the same as paused, which is your own on/off switch and never resets anything.
When it fails
StatusCodeMeaning
400invalid_requestA required field was missing or malformed.
422unsupported_valueThe url is not https, so the payload and its signature would travel in clear.
422unknown_valueAn event name is not in the catalogue.
curl -X POST \
  'https://acme.nolapro.com/!/api/v2/webhooks' \
  -H 'Authorization: Bearer $NP_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: your-unique-key' \
  -d '{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "enabled": false
}'
Response 201
{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "secret": "Example secret",
    "secret_note": "Example secret note"
}
GET/webhooks/{id} 200404

Retrieve one subscription.

Requires scope setup:read.

Parameters
NameInTypeNotes
idrequiredpathinteger
When it fails
StatusCodeMeaning
404not_foundNo subscription with that id.
curl \
  'https://acme.nolapro.com/!/api/v2/webhooks/104' \
  -H 'Authorization: Bearer $NP_TOKEN'
Response 200
{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "enabled": false
}
PATCH/webhooks/{id} 200404400422

Update a subscription.

Change the URL, the event list, or pause it. The secret cannot be read back; send a new one to rotate it.

Requires scope setup:write.

Parameters
NameInTypeNotes
idrequiredpathinteger
Body
FieldTypeDescription
idread-onlyintegerNolaPro id.
urlstring(500)HTTPS endpoint to POST to. Plain HTTP is refused - the payload carries business data and the signature is worthless over a channel anyone can read.
eventsarray of stringEvent types to receive. * is not accepted: naming them is what stops a new event type silently appearing in your handler.
descriptionstring(100)What this subscription is for. Shown on the token screen.
pausedbooleanPause delivery without cancelling the subscription. A paused subscription drops events rather than queueing them - it is a tap, not a buffer. Deliberately not called active: D23 reserves that word, and cancel already means something else here.
failcountread-onlyintegerConsecutive failed deliveries. Resets on the first success.
lastdeliverydateread-onlystringLast successful delivery.
disableddateread-onlystringSet when we gave up. See the disabling rule in Webhooks.
cancelread-onlybooleanCancelled.
enabledrequest-onlybooleanWrite-only. Send true to revive a subscription we disabled after six failed deliveries: it clears disableddate AND resets failcount, so it gets a fresh six. Clearing the date alone would disable it again on the next failure. Not the same as paused, which is your own on/off switch and never resets anything.
When it fails
StatusCodeMeaning
404not_foundNo subscription with that id.
400invalid_requestSend at least one field to change.
400unknown_parameterThe secret cannot be changed; create a new subscription.
422unsupported_valueThe url is not https.
422unknown_valueAn event name is not in the catalogue.
curl -X PATCH \
  'https://acme.nolapro.com/!/api/v2/webhooks/104' \
  -H 'Authorization: Bearer $NP_TOKEN' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: your-unique-key' \
  -d '{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "enabled": false
}'
Response 200
{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "enabled": false
}
DELETE/webhooks/{id} 200404409

Cancel a subscription.

Requires scope setup:cancel.

Parameters
NameInTypeNotes
idrequiredpathinteger
When it fails
StatusCodeMeaning
404not_foundNo subscription with that id.
409cannot_cancelAlready cancelled.
curl -X DELETE \
  'https://acme.nolapro.com/!/api/v2/webhooks/104' \
  -H 'Authorization: Bearer $NP_TOKEN'
Response 200
{
    "url": "Example url",
    "description": "Example description",
    "paused": false,
    "enabled": false
}
POST/webhooks/{id}/test 200400404422

Send a test delivery.

Delivers a webhooks.test event to the subscription URL right now and returns what your endpoint answered, including the status code and the first 500 bytes of the body. Built because "it is not firing" is the most common webhook ticket and it is almost always a 500 on the receiving end that nobody could see.

Requires scope setup:write.

Parameters
NameInTypeNotes
idrequiredpathinteger
When it fails
StatusCodeMeaning
400unknown_parameterThe body carried a field this endpoint does not take.
404not_foundThe subscription went away between the lookup and the write.
422unknown_valueThe id did not resolve to a subscription this token can see.
curl -X POST \
  'https://acme.nolapro.com/!/api/v2/webhooks/104/test' \
  -H 'Authorization: Bearer $NP_TOKEN'
POST/webhooks/{id}/flush 200400404422

Deliver this subscription's queue now.

Runs the delivery lane for this subscription immediately instead of waiting for the next five-minute cycle, and clears any remaining retry wait first. That second part is the point. If your endpoint was down and you have just fixed it, the event at the head of the queue may be sitting out a retry gap, and everything behind it is waiting on that event. Flushing drops the wait and sends in order from wherever the queue stands. Order is preserved exactly as it is on a normal cycle: delivery stops at the first failure, and nothing behind a failing event is sent.

Requires scope setup:write.

Parameters
NameInTypeNotes
idrequiredpathinteger
When it fails
StatusCodeMeaning
400unknown_parameterThe body carried a field this endpoint does not take.
404not_foundThe subscription went away between the lookup and the write.
422unknown_valueThe id did not resolve to a subscription this token can see.
curl -X POST \
  'https://acme.nolapro.com/!/api/v2/webhooks/104/flush' \
  -H 'Authorization: Bearer $NP_TOKEN'
Response 200
{
    "sent": 4,
    "failed": 0,
    "pending": 0
}
GET/webhooks/{id}/deliveries 200

What we tried to send, and what your endpoint said.

Every delivery attempt for this subscription, newest first, with the status code and the first 500 bytes your endpoint returned. This is the answer to "the webhook is not firing". It is almost always a 500 on the receiving end that never reached your own logs, and this shows it without anyone having to reproduce the problem. Delivered attempts are kept 7 days; ones that were dropped are kept 30 days, because a failure is what you need to look at later.

Requires scope setup:read.

Parameters
NameInTypeNotes
idrequiredpathintegerThe subscription.
deliveredquerystringtrue for successes, false for everything still owed or dropped.
abandonedquerystringtrue for events that ran out of attempts and were dropped.
eventtypequerystringOne event name, e.g. orders.created.
pagequeryinteger
perpagequeryinteger
curl \
  'https://acme.nolapro.com/!/api/v2/webhooks/104/deliveries' \
  -H 'Authorization: Bearer $NP_TOKEN'
POST/webhooks/{id}/rotatesecret 200400404422

Issue a new signing secret.

Mints a new secret and returns it once, the same rule as create. Both secrets sign for 24 hours by default, and that is the point rather than a nicety. You cannot change your stored secret at the same instant we change ours, so a straight swap guarantees a window where every delivery fails your signature check - and an event that runs out of attempts is dropped, not delayed. During the overlap NP-Signature carries two v1 values, the old one first, so a receiver that reads only the first keeps working untouched until you deploy. Send {"immediate": true} to skip the overlap if you can deploy the new secret atomically. Most cannot, which is why it is not the default.

Requires scope setup:write.

Parameters
NameInTypeNotes
idrequiredpathinteger
Body
FieldTypeDescription
immediaterequest-onlybooleanRetire the old secret at once instead of after 24 hours.
When it fails
StatusCodeMeaning
400unknown_parameterThe body carried a field this endpoint does not take.
404not_foundThe subscription went away between the lookup and the write.
422unknown_valueThe id did not resolve to a subscription this token can see.
curl -X POST \
  'https://acme.nolapro.com/!/api/v2/webhooks/104/rotatesecret' \
  -H 'Authorization: Bearer $NP_TOKEN'
Response 200
[]

Events #

Every event NolaPro can send. All share the one envelope described in Webhooks; only type and data differ.

EventScopeFires when
customers.createdcustomers:readA customer was created.
customers.updatedcustomers:readA customer changed. Includes credit limit and hold status.
customers.cancelledcustomers:readA customer was cancelled.
orders.createdorders:readAn order, quote or service order was created.
orders.updatedorders:readAn order header or any of its lines changed. Fires once for the order, not per line.
orders.cancelledorders:readAn order was cancelled. Committed stock has been released.
orders.invoicedinvoices:readAn order produced one or more invoices.
shipments.createdshipments:readA shipment went out. The 856 trigger.
shipments.cancelledshipments:readA shipment was cancelled and its stock movement reversed.
invoices.postedinvoices:readAn invoice posted to the ledger. The 810 trigger.
invoices.cancelledinvoices:readAn invoice was cancelled.
payments.createdpayments:readA payment was applied to an invoice.
payments.cancelledpayments:readA payment was reversed.
orderdeposits.createdpayments:readA deposit was taken against an order.
purchaseorders.createdpurchaseorders:readA purchase order was raised.
purchaseorders.updatedpurchaseorders:readA purchase order or one of its lines changed.
purchaseorders.cancelledpurchaseorders:readA purchase order was cancelled.
invreceives.createdpurchaseorders:readGoods were received against a purchase order.
items.createditems:readAn item was created.
items.updateditems:readAn item changed. Price and description changes land here.
items.stockchangeditems:readOn-hand or available quantity moved at a location. High volume - subscribe only if you mirror stock.
serviceordertimes.billedorders:readLogged hours were converted to order lines and billed.
itemidentifierdetails.createditems:readA serial number, lot or expiry value was recorded against an item.
itemidentifierdetails.updateditems:readAn identifier value changed, including its on-hand quantity at a location.