Webhooks

Event delivery, payload format, retries, signature validation

Hint uses webhooks to notify your application about important events in real time. Instead of polling for changes, register an endpoint and receive HTTP POST requests as events occur.

flowchart LR
    HintEvent["Hint event<br/>(patient.created, etc.)"] --> Queue[Webhook queue]
    Queue -->|Signed POST| PartnerEndpoint[Your webhook URL]
    PartnerEndpoint -->|2XX ack| HintAck[Delivery success]
    PartnerEndpoint -->|Non-2XX| Retry["Retry (backoff)"]
    Retry --> Queue
    classDef node fill:#ffffff,stroke:#16a34a,stroke-width:1px,color:#0f172a,font-size:12px
    class HintEvent,Queue,PartnerEndpoint,HintAck,Retry node

Endpoint Configuration

Configure your webhook URL using your partner API key. Hint supports two endpoint types:

Endpoint TypeScopeUse Case
Global endpointAll integrationsSingle endpoint receives events for every connected practice
Integration endpointSingle integrationSeparate endpoint per practice

Event Payload

All webhook events are sent as HTTP POST requests with JSON bodies:

{
  "id": "evt-jKi2jlalOJk3",
  "created_at": "2015-02-19T10:49:21.419-08:00",
  "type": "patient.created",
  "practice_id": "pra-6BIBTelN3rM5",
  "object": {
    "id": "pat-eN9DgCQ9iQxL",
    "first_name": "Jane",
    "last_name": "Doe"
  }
}

The object field contains the full resource representation—the same structure returned by the corresponding GET endpoint. For example, a patient.created event includes the same patient object you'd receive from GET /api/provider/patients/{id}.

Supported Events

Hint can send the following webhook events:

ObjectEvents
patientpatient.created, patient.updated, patient.destroyed, patient.inactive
membershipmembership.created, membership.updated, membership.destroyed
practitionerpractitioner.created, practitioner.updated, practitioner.destroyed
companycompany.created, company.updated, company.destroyed
employee_divisionemployee_division.created, employee_division.updated, employee_division.destroyed
customer_invoicecustomer_invoice.created, customer_invoice.updated, customer_invoice.destroyed, customer_invoice.draft, customer_invoice.issued, customer_invoice.paid, customer_invoice.cancelled
invoiceinvoice.created, invoice.updated, invoice.destroyed, invoice.draft, invoice.issued, invoice.paid, invoice.cancelled
signup_attemptsignup_attempt.created, signup_attempt.updated
integrationintegration.activated, integration.deactivated
📘

Configuring webhook events

To add or remove specific events from being sent, log into the Partner Portal:

Delivery & Retries

Respond with a 2XX status code to acknowledge receipt. Non-2XX responses trigger retries.

BehaviorDetails
Retry scheduleEvery hour for 72 hours
Endpoint mutingAfter 100 consecutive failures, the endpoint is muted for 1 hour
RecoveryNext successful delivery re-enables the endpoint
Failure alertsDaily email sent to technical contact on file
flowchart LR
    Active[Endpoint healthy] -->|Non-2XX responses| Warning[Alert email]
    Warning -->|Continued failures| Muted[Endpoint muted<br/>1 hour]
    Muted -->|Next delivery succeeds| Active
    Muted -->|Next delivery fails| Muted
    classDef node fill:#ffffff,stroke:#db2777,stroke-width:1px,color:#0f172a,font-size:12px
    class Active,Warning,Muted node

Failure Recovery

After an hour of muting, the next webhook delivery attempt determines the endpoint's state:

  • Success — Endpoint is automatically re-enabled
  • Failure — Endpoint remains muted for another hour (no additional email sent)

If the endpoint does not recover, Hint staff will reach out to help resolve the situation.

⚠️

Muted webhooks are not automatically re-attempted after recovery, as this could result in a large number of out-of-date events. Contact partner support if you need missed events replayed.

Security

Webhooks originate from Hint's servers, not browsers—there's no user session or cookies involved. CSRF protection doesn't apply and will reject every webhook if left enabled. Instead, authenticate webhooks by validating the X-Hint-Signature header.

Bypass CSRF, Validate Signatures

Exempt your webhook endpoint from CSRF checks and verify the HMAC signature:

class WebhookController < ApplicationController
  protect_from_forgery except: :webhook

  def webhook
    verify_signature!(request.raw_post)
    # Process webhook...
  end

  private

  def verify_signature!(payload)
    signature = request.headers['X-Hint-Signature']
    algorithm, digest = signature.split('=')
    expected = OpenSSL::HMAC.hexdigest(algorithm, ENV['HINT_PARTNER_API_KEY'], payload)
    head :forbidden unless Rack::Utils.secure_compare(expected, digest)
  end
end
import hashlib, hmac
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.http import HttpResponse, HttpResponseForbidden

@require_POST
@csrf_exempt
def webhook(request):
    if not verify_signature(request.body, request.headers.get('X-Hint-Signature')):
        return HttpResponseForbidden()
    # Process webhook...
    return HttpResponse(status=200)

def verify_signature(body, signature):
    expected = "sha256=" + hmac.new(
        key=HINT_PARTNER_API_KEY.encode(),
        msg=body,
        digestmod=hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
📘

Signature validation tips

  • Use constant-time comparison (secure_compare, hmac.compare_digest) to prevent timing attacks
  • Hash the raw request body, not a re-serialized object

Further Reading