POST · Bearer

POST /api/v1/webhooks

Summary

Register a URL for message.received, message.held, message.sent, message.blocked, message.bounced, message.complained, task.received, owner.reply, and domain.verified. message.held fires when screening is hold and the heuristic marks the mail suspicious. The message is stored. message.received fires for other stored inbound mail, and again when a held message is released. message.blocked fires when an allow or block list drops inbound mail, with inbox_id, from, subject, direction, and reason. message.bounced and message.complained fire when a delivery report matches an outbound message, with message_id, inbox_id, thread_id, bounce_type, and recipients. Optional headers (max 5, Authorization or X-*) are stored encrypted and sent on delivery; GET returns the names with values redacted. Optional inbox_id is stored into inbox_ids. inbox_ids accepts at most 10 inbox ids (400 too_many_inboxes). A hook matches when inbox_ids is empty or contains the event inbox. domain.verified has no inbox, so leave the filter empty to receive it. The same client_id returns the original hook with HTTP 200 and the secret redacted. GET /api/v1/webhooks/:id returns one hook with the secret redacted the same way as the list. PATCH accepts enabled: false disables deliveries and sets disabled_reason manual; enabled true re-enables and resets consecutive_failures to 0. A final non-2xx after retries increments consecutive_failures. At 20 the hook sets enabled to 0, disabled_at, and disabled_reason consecutive_failures. Any 2xx resets consecutive_failures to 0. Deliveries send x-cooper-signature (sha256= HMAC-SHA256 of the raw body), x-cooper-timestamp (unix seconds), and x-cooper-signature-v2 (v1= plus hex HMAC-SHA256 of timestamp + "." + raw body). Reject a timestamp more than 5 minutes from now and dedupe on x-cooper-delivery. 5xx and network errors are retried twice. Every delivery is stored for 7 days.

Request

{
  "url": "https://example.com/hooks/cooper",
  "events": [
    "message.received",
    "task.received"
  ],
  "inbox_ids": [
    "inb_…"
  ],
  "client_id": "hook-1",
  "headers": {
    "Authorization": "Bearer …",
    "X-Tenant": "acme"
  }
}

Response

The OpenAPI document does not declare a JSON response schema for this call. The summary is the contract.

Errors

  • HTTP 400 too_many_inboxes

Idempotency

client_id makes this write idempotent. Sending the same client_id again returns the original object instead of creating a duplicate.