Webhooks

Register HTTPS endpoints that receive signed POST callbacks when events happen in your account.

Verifying signatures

Every delivery carries an X-Byomailer-Signature header of the form sha256=<hex>, plus X-Byomailer-Timestamp (Unix seconds), X-Byomailer-Event, and X-Byomailer-Delivery (a unique id). Compute the expected signature as HMAC-SHA256(timestamp + "." + rawBody, secret) using your endpoint's signing secret, and compare it with a timing-safe comparison. Reject deliveries whose timestamp is outside a ±5 minute window to prevent replay.

Signature verification

// Node.js example
const crypto = require('crypto');
const expected = 'sha256=' + crypto
  .createHmac('sha256', secret)
  .update(timestamp + '.' + rawBody)
  .digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
GET/api/v1/webhook-endpoints

List all webhook endpoints for the authenticated client. Admin-only. Cursor-paginated. The signing secret is never returned by this endpoint.

Response Fields

idinteger

Endpoint ID.

uuidstring

Endpoint UUID.

urlstring

Destination HTTPS URL.

eventsarray

Subscribed event types.

activeboolean

Whether the endpoint receives deliveries.

disabled_reasonstring|null

manual or auto_failures when inactive.

consecutive_failuresinteger

Failed deliveries since the last success.

Request

curl -X GET \
  "https://byomailer.com/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json"

Response

200
{
  "data": [
    {
      "id": 1,
      "uuid": "wh-uuid-1",
      "url": "https://example.com/webhooks/byomailer",
      "description": "CRM sync",
      "events": [
        "contact.subscribed",
        "newsletter.sent"
      ],
      "active": true,
      "disabled_reason": null,
      "consecutive_failures": 0,
      "last_triggered_at": "2026-01-15T10:30:00+00:00"
    }
  ],
  "meta": {
    "path": "https://byomailer.com/api/v1/webhook-endpoints",
    "per_page": 25,
    "next_cursor": null,
    "prev_cursor": null
  }
}
POST/api/v1/webhook-endpoints

Create a webhook endpoint. The plaintext signing secret is returned ONCE in this response — store it to verify the X-Byomailer-Signature header. URL must be a public HTTPS endpoint. Supported events: contact.subscribed, contact.unsubscribed, contact.resubscribed, newsletter.sent, sequence_step.sent, sequence.completed.

Request

curl -X POST \
  "https://byomailer.com/api/v1/webhook-endpoints" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/byomailer",
  "events": [
    "contact.subscribed",
    "newsletter.sent"
  ],
  "description": "CRM sync"
}'

Response

201
{
  "data": {
    "id": 1,
    "uuid": "wh-uuid-1",
    "url": "https://example.com/webhooks/byomailer",
    "events": [
      "contact.subscribed",
      "newsletter.sent"
    ],
    "active": true,
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
}
PATCH/api/v1/webhook-endpoints/{id}

Update an endpoint. Only active and description are mutable — URL and events are immutable, recreate the endpoint to change them.

Request

curl -X PATCH \
  "https://byomailer.com/api/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "active": false,
  "description": "Paused for maintenance"
}'

Response

200
{
  "data": {
    "id": 1,
    "active": false
  }
}
DELETE/api/v1/webhook-endpoints/{id}

Delete an endpoint and stop all deliveries to it.

Request

curl -X DELETE \
  "https://byomailer.com/api/v1/webhook-endpoints/{id}" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json"

Response

204
No content
GET/api/v1/webhook-endpoints/{id}/deliveries

Cursor-paginated delivery log for an endpoint.

Request

curl -X GET \
  "https://byomailer.com/api/v1/webhook-endpoints/{id}/deliveries" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json"

Response

200
{
  "data": [
    {
      "id": 10,
      "uuid": "del-uuid-10",
      "event_type": "contact.subscribed",
      "status": "success",
      "attempts": 1,
      "response_status": 200,
      "delivered_at": "2026-01-15T10:30:01+00:00"
    }
  ],
  "meta": {
    "per_page": 25,
    "next_cursor": null,
    "prev_cursor": null
  }
}
POST/api/v1/webhook-endpoints/{id}/test

Queue a sample test delivery to the endpoint. Rate-limited per client.

Request

curl -X POST \
  "https://byomailer.com/api/v1/webhook-endpoints/{id}/test" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json"

Response

202
{
  "data": {
    "id": 11,
    "event_type": "webhook.test",
    "status": "pending"
  }
}
POST/api/v1/webhook-endpoints/{id}/secret

Rotate the signing secret. The new plaintext secret is returned ONCE; existing receivers fail signature verification until updated.

Request

curl -X POST \
  "https://byomailer.com/api/v1/webhook-endpoints/{id}/secret" \
  -H "Authorization: Bearer {your-api-token}" \
  -H "Accept: application/json"

Response

200
{
  "data": {
    "id": 1,
    "secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  }
}
BYOMailer

© 2026 All rights reserved.