Webhooks

Subscribe external systems to events inside DM Editors — entry publishes, form submissions, media uploads — with signed payloads, retries, and a delivery log.

Routes

RouteMethodPurpose
/admin/webhooksGETList subscriptions
/admin/webhooks/createGETNew subscription
/admin/webhooksPOSTCreate
/admin/webhooks/{webhook}GETSubscription detail + delivery log
/admin/webhooks/{webhook}/editGETEdit
/admin/webhooks/{webhook}PUTUpdate
/admin/webhooks/{webhook}DELETEDelete
/admin/webhooks/{webhook}/toggle-activePATCHPause / resume deliveries
/admin/webhooks/{webhook}/testPOSTSend a sample payload to the URL

Subscription attributes

Available events

EventTrigger
entry.createdNew content entry saved
entry.updatedEntry edited
entry.publishedEntry transitions to published
entry.unpublishedEntry transitions away from published
entry.deletedEntry soft-deleted
form.submission.createdNew form submission
media.uploadedFile uploaded
media.deletedFile deleted
user.createdNew admin user

Payload shape

POST https://example.com/webhooks/DM Editors
Content-Type: application/json
X-DM Editors-Event:     entry.published
X-DM Editors-Delivery:  <uuid>
X-DM Editors-Signature: sha256=<hex hmac of body using webhook.secret>
User-Agent:        DM Editors-Webhooks/1.0

{
  "event":      "entry.published",
  "occurred_at": "2026-05-29T10:15:00Z",
  "data": {
    "id":           "…",
    "model_slug":   "blog-posts",
    "title":        "Hello",
    "slug":         "hello",
    "locale":       "en",
    "status":       "published",
    "published_at": "2026-05-29T10:14:58Z"
  }
}

Signature verification

// Node.js example
const crypto = require('crypto');

function verify(req, secret) {
  const sig = req.headers['x-DM Editors-signature'];
  const computed = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(req.body))
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(computed));
}

Delivery log

webhook_deliveries stores one row per attempt: target URL, payload, response code, response body (truncated), latency, and attempt number. Retries are queued via Laravel's job system; failed deliveries keep retrying with exponential backoff until they succeed or hit the retry cap.

Testing

The "Send test" button posts a synthetic payload to the URL so you can verify connectivity without waiting for a real event. The response is shown inline and logged like any other delivery.