Skip to main content
Rather than polling, register an endpoint and DMLY will POST to it whenever one of your sub-accounts changes: a workspace is created, suspended or restored, or a subscription or add-on changes. These are agency webhooks, separate from the workspace webhooks that fire on activity inside a workspace.

The events

There are six: The suspend, restore, plan and add-on events fire whether the change was made in the agency console or over the Agency API, so one subscription keeps you in sync with both. workspace.created is the exception: the console has no create button, so it fires only from a branded sign-up or from the API.
workspace.created fires only for a brand-new sub-account created by a branded sign-up or by POST /workspaces. Two other ways a workspace can join your agency do not fire it: an existing customer adding a second workspace from the workspace switcher, and your own workspace being moved under your agency when you took a reseller plan. Both appear in your sub-accounts list without a webhook, so if you must catch every workspace, reconcile periodically against GET /workspaces. Client workspaces covers all three arrival paths.

Setting one up

You can manage webhook endpoints from the agency console, under Reseller Dashboard → Webhooks, or over the API with the /webhooks endpoints in the sidebar. In the console each endpoint’s row carries Test, Rotate secret, Edit and Remove.
1

Register an endpoint

In the console, select + Add webhook. Add your endpoint URL (it must be publicly reachable over HTTPS) and subscribe to the events you care about.
2

Store the signing secret

A signing secret is generated for you and returned once, on creation. Store it; you use it to verify every delivery. If you lose it, select Rotate secret on the endpoint’s row to get a new one, which invalidates the old one.
3

Send a test

Use the test event to confirm your endpoint is reachable and your signature check works before relying on it. In the reseller dashboard, select Test on the endpoint’s row. A test payload carries an extra "test": true.

The payload

Every delivery has the same envelope. Only data varies by event:
The subscription.changed payload adds the new plan; addon.granted and addon.revoked add an addon object. Each event page in the sidebar shows its exact payload.

Headers

Verifying a delivery

The signature is an HMAC-SHA256 of the raw request body, keyed by your endpoint’s secret. Compute it over the bytes exactly as received; parsing and re-serialising the JSON first changes the bytes, and the signature will never match.
Always compare with a constant-time function (hmac.compare_digest, crypto.timingSafeEqual). A plain == leaks timing information that can be used to forge a signature.

Responding

Return any 2xx to acknowledge. A non-2xx (or a timeout) is treated as a failure and retried twice more, after roughly 10s then 60s (three attempts in total). After the third the delivery is marked failed and not retried again. Nothing is lost silently: every attempt is recorded. In the reseller dashboard, open Webhooks and look under Recent deliveries, which lists the last 30 deliveries across all your endpoints with columns Webhook, Event, Result and When. Result shows the outcome and the HTTP status your endpoint returned, for example error · 500. The section only appears once there has been at least one delivery. Each endpoint’s row also shows its most recent outcome inline, as last: success or last: error. One case is never retried. DMLY re-checks your endpoint’s host at delivery time, because DNS can change between saving the endpoint and sending to it. If that check fails, nothing is sent: the delivery shows in Recent deliveries as an error with no HTTP status, and no further attempt is made.
Deliveries are at-least-once: a timeout on your side can produce a retry for an event you already handled. Deduplicate on the envelope’s id (the same value as the X-Dmly-Delivery header), which is stable across retries.

Events

Each event is documented with its full payload in the sidebar under this section; the list is generated from the API itself, so it is never out of date.