Gareth Wilson Gareth Wilson

Guide to Coterie Webhooks: Features and Best Practices

Published


Coterie webhooks keep a partner's systems in step with Coterie Insurance: an application becomes ready to quote, a quote is priced, or a policy is issued. If you sell or embed Coterie's small-business commercial insurance through its Commercial Insurance API, webhooks are how those updates reach your platform in real time instead of through polling.

This guide covers how Coterie webhooks are enabled and authenticated, how to tell the three kinds of webhook apart, what Coterie leaves undocumented, and the practices that keep a Coterie receiver reliable.

What are Coterie webhooks?

Coterie uses webhooks "for three different actions; applications, quotes, and policies". Each webhook body is the resource itself, in the same shape the API uses: an application object, a quote object or a policy object. There is no envelope, no event name and no type field.

Webhooks are not self-serve. Coterie's documentation describes no dashboard or API for registering an endpoint, and its optional headers "must be turned on by request only", so you set webhooks up with your Coterie contact.

Coterie does not sign its payloads. The only authentication is an optional static header named Authentication, sent as Authentication: token {guid}, which your receiver compares with the token it expects.

Coterie webhook features

FeatureDetails
ConfigurationThrough your Coterie contact
KindsApplications, quotes and policies
Event identityNone on the wire. Classify by the ID key in the body
BodyThe bare API resource, JSON
AuthenticationOptional Authentication: token {guid} header, enabled on request
SignatureNone
Optional headersx-timeStamp, x-applicationId, x-leadId, each enabled on request
Timestampx-timeStamp, epoch milliseconds, not signed
Method, timeout, retriesNot documented
Source IPsNot published

Common events

Because no field names the kind of webhook, classify each body by the identifier it carries, in this order:

KindRecognise it byNotable fields
PolicyPolicyIdPolicyNumber, Status (for example Pending), StartDate, EndDate, Premium
QuotequoteIdPolicyType, Premium, MonthlyPremium, IsEstimate, LineItems, applicationId
ApplicationapplicationId, with neither of the aboveStatus (for example ReadyToQuote), BusinessName, ApplicationTypes, Locations

The order matters. A quote body also carries applicationId, so checking for applicationId first files every quote as an application.

Here is a trimmed version of Coterie's sample quote webhook:

{
  "quoteId": "323d6e60-cb75-4098-af71-a643d8f47dba",
  "PolicyType": "BOP",
  "ExpirationDate": null,
  "IsEstimate": true,
  "Premium": 2699,
  "MonthlyPremium": null,
  "ApplicationUrl": "https://quote-test.coterieinsurance.com/application/83857350-4bfb-4669-862c-61f05a7a4be9",
  "applicationId": "83857350-4bfb-4669-862c-61f05a7a4be9",
  "LineItems": [
    {
      "Description": "BPP Premium",
      "Amount": 74.97,
      "LineItemType": "Premium",
      "PremiumType": "BPP"
    }
  ]
}

Keys are mixed-case: applicationId and quoteId start in lower case, but PolicyId, Status and most other fields don't. Match them exactly.

Application and policy bodies carry a Metadata field. It is a JSON-encoded string, not an object, holding whatever you attached to the application, for example {"partner_id": "...", "lead_id": "...", "customer_id": "CUSTID123"}. Parse it a second time to get your own IDs back. If you ask for the x-leadId header, Coterie sends your lead ID from that metadata as a header too.

Coterie doesn't document which changes trigger each kind of webhook, so a policy webhook can't tell you by itself whether the policy was just created or changed. Compare Status with what you stored last time.

Setting up Coterie webhooks

  1. Give your Coterie contact your HTTPS endpoint URL and the kinds of webhook you need.
  2. Ask them to enable the Authentication header, and confirm the exact token value with them. Coterie's documentation doesn't say which side generates the GUID.
  3. Store the full header value, including the token prefix, for example as COTERIE_AUTHENTICATION=token 1a2b3c4d-....
  4. Optionally, ask for x-timeStamp, x-applicationId and x-leadId.

The sample payloads point at quote-test.coterieinsurance.com, so Coterie runs a separate test environment. Confirm with your contact which environment sends to which endpoint, and keep a token for each.

If you use Hookdeck, create a Coterie source and paste the full header value into its Authentication Header Value field, including the token prefix, as in token <guid>. Hookdeck compares the whole value, so a bare GUID never matches. Use the source URL as your webhook endpoint. If the Authentication header hasn't been enabled yet, leave verification off on the source until it is.

Hookdeck doesn't check this header at the edge. It accepts the request and answers Coterie with 200, then records any request with a missing or wrong token as failed verification instead of delivering it to your service.

Securing Coterie webhooks

There is nothing to compute. Read the Authentication header (not Authorization) and compare the whole value with the token you stored, in constant time. Because the body is not signed, you can parse it normally after the check.

const crypto = require('crypto');
const express = require('express');

function verifyCoterieToken(headerValue, expectedValue) {
  if (!headerValue || !expectedValue) return false;
  const received = Buffer.from(headerValue, 'utf8');
  const expected = Buffer.from(expectedValue, 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

function classifyCoterieWebhook(body) {
  if ('PolicyId' in body) return 'policy';
  if ('quoteId' in body) return 'quote'; // check before applicationId
  if ('applicationId' in body) return 'application';
  return 'unknown';
}

const app = express();

app.post('/webhooks/coterie', express.raw({ type: '*/*' }), (req, res) => {
  const expected = process.env.COTERIE_AUTHENTICATION; // "token <guid>"
  if (!expected) return res.sendStatus(500); // fail closed

  if (!verifyCoterieToken(req.get('authentication'), expected)) {
    return res.sendStatus(401);
  }

  let body;
  try {
    body = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }
  const kind = classifyCoterieWebhook(body);
  // Persist, then process async.
  res.sendStatus(200);
});

The same check in Python:

import hmac


def verify_coterie_token(header_value, expected_value: str) -> bool:
    if not header_value or not expected_value:
        return False
    return hmac.compare_digest(header_value.encode("utf-8"), expected_value.encode("utf-8"))


def classify_coterie_webhook(body: dict) -> str:
    if "PolicyId" in body:
        return "policy"
    if "quoteId" in body:
        return "quote"
    if "applicationId" in body:
        return "application"
    return "unknown"


# In FastAPI: verify_coterie_token(request.headers.get("authentication"),
#                                  os.environ["COTERIE_AUTHENTICATION"])

The token is a static credential sent with every request. It proves only that the sender knows it, it doesn't protect the body, and anyone who captures one request can send others. Serve the endpoint over HTTPS only and treat the token like a password.

Make Coterie webhooks production-ready. Hookdeck Event Gateway checks the Authentication token, answers Coterie straight away, and durably queues every application, quote and policy update before your service sees it.

Coterie webhook limitations and pain points

A static token instead of a signature

The Problem: Nothing proves the body came from Coterie unaltered, and without the optional header nothing authenticates the request at all.

Why It Happens: Coterie doesn't sign payloads. The Authentication header is the only credential, it's the same on every request, and it is off until you ask for it.

Workarounds:

  • Ask for the Authentication header before you go live, and reject requests without it.
  • Treat a webhook as a notification for anything that moves money or coverage, and confirm the policy or quote through the Coterie API before acting.
  • Keep the token out of logs and rotate it with your contact if it may have leaked.

How Hookdeck Can Help: Hookdeck checks the token for you, so your own services never hold it, and keeps requests with a missing or wrong token away from your service.

No type field

The Problem: Quotes are processed as applications, or unexpected bodies crash the handler.

Why It Happens: Coterie sends the bare resource, so the only clue to its kind is which ID key is present. The quote body contains applicationId as well as quoteId.

Workarounds:

  • Classify by PolicyId, then quoteId, then applicationId, and send anything else to an "unknown" path that logs and acknowledges.
  • Write a test for each of Coterie's sample bodies.

How Hookdeck Can Help: Hookdeck filters can route on the presence of PolicyId or quoteId, so each kind of webhook can go to its own destination.

Undocumented delivery behaviour

The Problem: You don't know what response Coterie expects, how long it waits, or whether it retries.

Why It Happens: Coterie's documentation covers the payloads and headers but says nothing about delivery: no method, timeout, retry schedule or failure handling.

Workarounds:

  • Respond with 200 quickly and process asynchronously.
  • Ask your Coterie contact about retries and timeouts, and record the answer with your integration notes.
  • Reconcile open applications and policies through the API on a schedule.

How Hookdeck Can Help: Hookdeck accepts each webhook as soon as Coterie sends it and stores it, then retries delivery to your service on a schedule you control and lets you replay anything that failed.

No event ID

The Problem: You can't tell a duplicate from a genuine second update.

Why It Happens: Bodies carry the resource ID but no event ID, and x-timeStamp is optional and unsigned.

Workarounds:

  • Make updates idempotent: store the latest Status and other key fields per resource, and ignore a webhook that changes nothing.
  • If you enable x-timeStamp, use it with the resource ID to order updates, not to authenticate them.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the body fields you choose, such as PolicyId and Status.

Best practices

Store the full token, prefix included

The header value is token {guid}. Store and compare the whole string. Comparing a bare GUID, or reading Authorization instead of Authentication, fails every request.

Parse Metadata twice

Metadata is a string containing JSON. Parse the body, then parse Metadata, and handle null, which the sample policy body uses.

Keep test and production apart

Use separate endpoints, or at least separate tokens, for Coterie's test and production environments, and check ApplicationUrl or your own metadata before acting on a webhook.

Acknowledge fast and process asynchronously

Coterie documents no timeout, so keep the request path short: check the token, store the body, return 200. See why to process webhooks asynchronously and our guide to webhook idempotency.

Conclusion

Coterie sends the application, quote or policy object itself, with no type field and no signature. Ask your Coterie contact to enable the Authentication: token {guid} header, compare the whole value in constant time, classify each body by PolicyId, then quoteId, then applicationId, and confirm important changes through the API, since Coterie documents no retry behaviour.

Hookdeck Event Gateway sits in front of your receiver, checks the Coterie token, and holds each webhook until your service has processed it.

Get started with Hookdeck for free and handle Coterie webhooks reliably in minutes.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.