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 Type | Scope | Use Case |
|---|---|---|
| Global endpoint | All integrations | Single endpoint receives events for every connected practice |
| Integration endpoint | Single integration | Separate 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:
| Object | Events |
|---|---|
patient | patient.created, patient.updated, patient.destroyed, patient.inactive |
membership | membership.created, membership.updated, membership.destroyed |
practitioner | practitioner.created, practitioner.updated, practitioner.destroyed |
company | company.created, company.updated, company.destroyed |
employee_division | employee_division.created, employee_division.updated, employee_division.destroyed |
customer_invoice | customer_invoice.created, customer_invoice.updated, customer_invoice.destroyed, customer_invoice.draft, customer_invoice.issued, customer_invoice.paid, customer_invoice.cancelled |
invoice | invoice.created, invoice.updated, invoice.destroyed, invoice.draft, invoice.issued, invoice.paid, invoice.cancelled |
signup_attempt | signup_attempt.created, signup_attempt.updated |
integration | integration.activated, integration.deactivated |
Configuring webhook eventsTo 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.
| Behavior | Details |
|---|---|
| Retry schedule | Every hour for 72 hours |
| Endpoint muting | After 100 consecutive failures, the endpoint is muted for 1 hour |
| Recovery | Next successful delivery re-enables the endpoint |
| Failure alerts | Daily 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
endimport 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
- WebhookRequest — View webhook event history via API
- Global Webhook Endpoint — Configure partner-level endpoints
- Integration Webhook Endpoint — Configure per-integration endpoints
- Best Practices — Integration patterns and sync status tracking
