Webhooks

Webhooks

Webhooks let you receive real-time event callbacks whenever data changes in your Coastline workspace. Register a target URL with a trigger, and Coastline will POST a JSON payload to that URL each time the event fires. Use webhooks to push data into Zapier, Make, n8n, your own backend, or any HTTP endpoint you control.

How it works

  1. Create an API key in Settings → API Keys.
  2. Subscribe to a trigger by sending a POST /v1/subscriptions with a trigger_id and your target_url.
  3. Coastline POSTs a signed JSON payload to your URL each time the event fires.
  4. Respond with an HTTP 2xx within 15 seconds to acknowledge delivery.

See the subscriptions reference for the full create / list / revoke API.

Available triggers

The trigger_id field on a subscription must match one of the values below.

trigger_idLabelDescription
opportunity.createdNew OpportunityFires when a new opportunity (lead) is created.
opportunity.project.stage.updatedOpportunity Stage ChangedFires whenever an opportunity or project moves to a new pipeline stage.
opportunity.project.approvedProject ApprovedFires when an opportunity is approved and converted into a project.
opportunity.estimate_sentEstimate SentFires when an estimate is sent to a customer.
opportunity.estimate_approvedEstimate ApprovedFires when a customer approves an estimate.
opportunity.estimate_cancelledEstimate CancelledFires when an estimate is cancelled.
opportunity.d4d.spottedNew Lead Spotted (Drive Mode)Fires when a new lead is captured in Drive Mode.
envelope.signedDocument SignedFires when a recipient signs a document.
envelope.completedDocument Fully SignedFires when every recipient has signed a document.
call.inbound.completedInbound Call AnsweredFires when the AI assistant finishes answering a call to the workspace number.
call.outbound.completedOutbound Call CompletedFires when an AI-placed call connects and finishes.
call.missedCall MissedFires when a call ends without a conversation (abandoned inbound or unanswered outbound).
expense.createdNew ExpenseFires when a purchase or mileage expense is created. The receipt is attached in a second step, so this event usually carries no receipt yet.
expense.receipt_attachedExpense Receipt CapturedFires when a receipt lands on an expense that had none. This is the event to subscribe to if your integration needs the receipt image.
expense.updatedExpense UpdatedFires when a business field on an expense changes. A reconciliation write-back does not count as a change, so stamping an expense never re-fires this.
expense.deletedExpense DeletedFires when an expense is removed. data is null because the record is gone; the fields are in metadata.
photo.uploadedNew Project PhotoFires the moment a photo or video is fetchable on a project. A row can exist before its file does, so this waits for the bytes to land rather than firing on creation.
photo.deletedProject Photo DeletedFires when a photo is moved to the trash. Restoring one fires photo.uploaded again, so a mirror that dropped it puts it back.

Payload shape

Every webhook delivery is a JSON object with the same envelope. The data field contains the full record that triggered the event (e.g. the opportunity row for an opportunity event).

FieldTypeDescription
idstringUnique event id. Use this to deduplicate.
triggerstringThe trigger_id this event matches (e.g. opportunity.created).
entity_typestringThe entity that emitted the event (opportunity, contact, task, contractor, envelope, expense, photo).
entity_idstringUUID of the entity record.
occurred_atstringISO 8601 timestamp of when the event happened.
performed_bystring | nullUUID of the workspace user who took the action, or null for system events.
metadataobjectTrigger-specific context (e.g. previous_stage_id, new_stage_id for stage changes).
dataobject | nullThe full record that triggered the event. Same shape returned by the resource endpoints.

Acting on a delivery alone

A handler that has to make three authenticated calls before it can do its job fails partway through three times as often, so data carries what a consumer actually needs:

  • Expense events carry the full expense object, including a signed receipt URL that needs no authentication to fetch, and the linked project with its address and custom fields. It is the same shape GET /v1/expenses returns, so one field mapping serves both.
  • Photo events carry the photo with signed links to the original and its display and thumbnail renditions, plus a content_hash so an integration mirroring photos elsewhere can tell what it already has without keeping its own ledger. Same shape GET /v1/photos returns.
  • Opportunity events carry data.custom_fields, resolved to stable field keys. See custom fields.
  • Delete events have data: null, since the record no longer exists to read. The fields it held are in metadata.

Signed receipt URLs are valid for 24 hours and are minted fresh on every delivery attempt, so a retried delivery never carries an expired link. Treat one as a credential: anyone holding it can fetch the image until it expires.

Signature verification

Every request includes an X-Coastline-Signature header of the form sha256=<hex>. The signature is an HMAC-SHA256 of the raw request body, keyed by the signing_secret returned when you created the subscription. Verify the signature on every request, never trust an unsigned payload.

You will also receive these headers on each delivery:

HeaderDescription
X-Coastline-Signaturesha256=<hex> HMAC of the request body.
X-Coastline-EventThe trigger_id of the event (e.g. opportunity.created).
X-Coastline-Event-IdUnique event id, same as the id field in the payload. Use to deduplicate.
User-AgentCoastlineCRM-Webhooks/1.0

Retries and delivery

  • Coastline expects a 2xx response within 15 seconds.
  • Failed deliveries are retried with exponential backoff at 1, 5, 15, 60, and 240 minutes (6 attempts total).
  • Permanent 4xx responses (other than 408 / 429) are not retried.
  • Respond with HTTP 410 Gone to permanently revoke a subscription from the receiver side.
  • Deliveries are at-least-once. Use the id field to deduplicate.

Deduplicating on id only protects a single process that remembers what it has seen. If handling an event creates a record in another system, keep the durable proof on the Coastline record itself: stamp the expense with an external reference and skip anything already stamped. That is what keeps a retry, a replay, and an overlapping nightly sweep from all creating the same purchase order.

Sample payloads

Fetch up to three of your most recent real events for any trigger via GET /v1/triggers/{trigger_id}/sample. If your workspace has not yet emitted that trigger, a synthetic payload is returned so you can map fields ahead of time.