Clearmargin
API

Webhook Events

All webhook event types, payloads, and signature verification for Clearmargin integrations.

Webhooks send real-time HTTP POST notifications to your server when events occur in Clearmargin. Use them to trigger workflows, sync data, and build custom integrations.

Configuration

Configure webhooks in your dashboard at Settings > Webhooks. For each endpoint you can:

  • Set a destination URL that receives POST requests
  • Choose which events to subscribe to
  • Add a signing secret for HMAC-SHA256 signature verification
  • Enable or disable the endpoint without deleting it

Signature Verification

When you configure a signing secret, Clearmargin includes two headers on every delivery for verification:

HeaderDescription
X-SignatureHMAC-SHA256 signature in the format sha256=hex_digest
X-Webhook-TimestampUnix timestamp (seconds) for replay protection

The signature is computed over {timestamp}.{body}. Verify it like this:

const crypto = require('crypto')

function verifyWebhookSignature(body, timestamp, signature, secret) {
  // Reject old timestamps (> 5 minutes) to prevent replay attacks
  const now = Math.floor(Date.now() / 1000)
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error('Timestamp too old')
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${body}`)
    .digest('hex')

  const received = signature.replace('sha256=', '')

  if (
    !crypto.timingSafeEqual(
      Buffer.from(expected, 'hex'),
      Buffer.from(received, 'hex'),
    )
  ) {
    throw new Error('Invalid signature')
  }
}

Always verify signatures

If you configure a signing secret, always verify the signature before processing the payload. Use timingSafeEqual to prevent timing attacks.

Event Types

Proposal Events

EventDescription
proposal_sentA proposal has been sent to a client
proposal_expiringA proposal is about to expire
proposal_acceptedA client has accepted a proposal
proposal_rejectedA client has rejected a proposal
proposal_expiredA proposal has passed its expiration date

Invoice & Payment Events

EventDescription
invoice_sentAn invoice has been sent to a client
invoice_due_soonAn invoice is approaching its due date
invoice_overdueAn invoice has passed its due date
invoice_paidA client has paid an invoice
invoice_refundedA payment has been refunded
invoice_payment_failedA payment attempt has failed
invoice_disputedA payment has been disputed

Receipt Events

EventDescription
receipt_sentA receipt has been sent to a client

Payment Schedule Events

EventDescription
schedule_deposit_readyA deposit invoice is ready
schedule_installment_dueAn installment payment is due
schedule_installment_paidAn installment payment has been received
schedule_completedAll payments in a schedule are complete

Document Intelligence Events

EventDescription
document_processedA document has finished AI processing
document_needs_reviewA document has actions requiring manual review
document_failedDocument processing has failed
document_action_createdAn AI-proposed action is awaiting approval
document_action_approvedAn AI-proposed action has been approved

Entity Events

EventDescription
client_createdA new client has been created
project_createdA new project has been created
time_entry_createdA new time entry has been logged

Inquiry Events

EventDescription
inquiry_createdA new inquiry has been submitted
inquiry_status_changedAn inquiry's status has changed
inquiry_convertedAn inquiry has been converted to a client and project

Payload Format

All webhook deliveries use this envelope format:

{
  "event": "invoice_paid",
  "timestamp": "2026-03-14T15:30:00.000Z",
  "data": {
    "documentType": "invoice",
    "documentId": "inv_abc123",
    "documentNumber": "INV-0042",
    "amount": 2500.00,
    "client": {
      "id": "client_xyz",
      "name": "Acme Corp"
    },
    "contact": {
      "id": "contact_123",
      "name": "Jane Smith",
      "email": "jane@acme.com"
    }
  }
}

The data object varies by event type but always includes identifiers for the relevant resources. Payment-related events include additional fields:

{
  "event": "invoice_paid",
  "timestamp": "2026-03-14T15:30:00.000Z",
  "data": {
    "documentType": "invoice",
    "documentId": "inv_abc123",
    "documentNumber": "INV-0042",
    "amount": 2500.00,
    "client": {
      "id": "client_xyz",
      "name": "Acme Corp"
    },
    "contact": {
      "id": "contact_123",
      "name": "Jane Smith",
      "email": "jane@acme.com"
    },
    "paymentMethod": "card",
    "last4": "4242",
    "brand": "visa"
  }
}

Retry Policy

Delivery retries

Failed deliveries are retried up to 3 times with exponential backoff (30 seconds, 2 minutes, 10 minutes). Your endpoint must respond with a 200 status code within 10 seconds or the delivery is considered failed. Ensure your handler returns quickly --- offload heavy processing to a background job.

On this page