Skip to content

Webhooks API

Receive real-time delivery status notifications via HTTP webhooks.

Endpoints

  • POST /v1/webhooks — create webhook endpoint
  • GET /v1/webhooks — get webhook endpoint
  • PATCH /v1/webhooks/{uuid} — update webhook endpoint
  • DELETE /v1/webhooks/{uuid} — delete webhook endpoint

One webhook endpoint per application. All endpoints require authentication.


Event Types

EventWhenChannels
delivery.sentProvider accepted the messageAll
delivery.deliveredDelivery confirmed by providerEmail (SES confirmation)
delivery.failedSend failed or provider rejectedAll
delivery.bouncedHard bounce (contact suppressed)Email
delivery.complainedSpam complaint (contact suppressed)Email

delivery.delivered

delivery.delivered is only emitted when we receive actual delivery confirmation from the provider. For channels without delivery receipts (Slack, Discord, Push), you'll receive delivery.sent only.


Webhook Payload

Every webhook POST contains a JSON payload:

json
{
  "event": "delivery.sent",
  "delivery": {
    "uuid": "d3f1a2b4-5678-9012-3456-789012345678",
    "channel": "email",
    "status": "sent",
    "event_name": "welcome",
    "message_id": "provider-message-id",
    "sent_at": "2026-03-30T12:00:00.000Z",
    "delivered_at": null,
    "error": null
  },
  "request_id": "r8e5c1a2-1234-5678-9012-345678901234",
  "timestamp": "2026-03-30T12:00:00.123Z"
}

For failed deliveries, error contains structured details:

json
{
  "error": {
    "code": "HARD_BOUNCE",
    "message": "Permanent bounce: mailbox does not exist"
  }
}

Correlate webhook events with your original send request using delivery.uuid or request_id.


Signature Verification

Every webhook includes an HMAC-SHA256 signature for verification:

X-Sendivent-Signature: t=1711800000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Sendivent-Webhook-Id: unique-attempt-id
Content-Type: application/json
User-Agent: Sendivent-Webhook/1.0

Verifying signatures

  1. Extract t (timestamp) and v1 (signature) from the X-Sendivent-Signature header
  2. Compute the expected signature: HMAC-SHA256(signing_secret, "${t}.${raw_body}")
  3. Compare your computed signature with v1 using constant-time comparison
  4. Reject if the timestamp is older than 5 minutes (replay protection)
javascript
import crypto from 'crypto';

function verifyWebhook(rawBody, signatureHeader, signingSecret) {
  const parts = signatureHeader.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).substring(2);
  const signature = parts.find(p => p.startsWith('v1=')).substring(3);

  // Check timestamp tolerance (5 minutes)
  const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
  if (age > 300) return false;

  // Compute expected signature
  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

POST /v1/webhooks

Create a webhook endpoint. Returns the signing secret — store it securely, it is only shown on creation.

Request body

json
{
  "url": "https://your-app.com/webhooks/sendivent",
  "enabled_events": ["delivery.sent", "delivery.failed"],
  "description": "Production webhook"
}
FieldTypeRequiredDescription
urlstringYesHTTPS endpoint URL
enabled_eventsstring[]NoEvent types to receive (defaults to all)
descriptionstringNoHuman-readable label

Response (201)

json
{
  "success": true,
  "webhook": {
    "uuid": "wh-uuid-here",
    "url": "https://your-app.com/webhooks/sendivent",
    "enabled_events": ["delivery.sent", "delivery.failed"],
    "is_active": true,
    "description": "Production webhook",
    "signing_secret": "whsec_a1b2c3d4e5f6...",
    "created_at": "2026-03-30T12:00:00.000Z"
  }
}

Returns 409 if the application already has a webhook endpoint.


GET /v1/webhooks

Retrieve the webhook endpoint for your application.

Response (200)

json
{
  "success": true,
  "webhook": {
    "uuid": "wh-uuid-here",
    "url": "https://your-app.com/webhooks/sendivent",
    "enabled_events": ["delivery.sent", "delivery.failed"],
    "is_active": true,
    "description": "Production webhook",
    "created_at": "2026-03-30T12:00:00.000Z",
    "updated_at": "2026-03-30T14:00:00.000Z"
  }
}

Returns 404 if no webhook endpoint is configured.


PATCH /v1/webhooks/

Update a webhook endpoint. Only include fields you want to change.

Request body

json
{
  "url": "https://new-endpoint.com/webhooks",
  "enabled_events": ["delivery.sent", "delivery.delivered", "delivery.failed"],
  "is_active": false,
  "description": "Updated webhook"
}

All fields are optional.


DELETE /v1/webhooks/

Delete a webhook endpoint. Sendivent will stop sending events immediately.

Response (200)

json
{
  "success": true
}

Retry Behavior

If your endpoint returns a 5xx status code or times out (10 seconds), Sendivent retries up to 5 times with exponential backoff.

4xx responses are treated as permanent failures and are not retried.

See Also

Released under the MIT License.