Overview
Webhooks are a way for your application to receive real-time notifications when events occur in your account. When an event occurs, we send an HTTP POST request to the webhook’s configured URL. You can use webhooks to trigger custom code, workflows, or integrations in your application.Creating Webhooks
You can create and manage webhooks directly from the JustPaid dashboard:- Navigate to Settings → Developer → Webhooks
- Click Create Webhook URL
- Enter your webhook endpoint URL
- Select the event types you want to subscribe to
- Click Save
Two Different Naming Schemes
Common integration mistake
A receiver written like this will drop every JustPaid event:200 while ignoring the event means retries will not fire and the
delivery is recorded as successful on our side, so this failure mode is silent.
Log unmatched type values during integration to catch it.
Event Types
The table below is exhaustive. Every value listed is a real, deliverable event type; there are no others.Note that
CUSTOMER_CONTRACT_CREATED maps to contract.created — the wire value
drops the customer_ prefix. It is the one pair where the two names are not a
mechanical transformation of each other.Behavior worth knowing
invoice.created fires for DRAFT invoices. Creation is creation regardless of
status — the event is emitted as soon as the invoice row is committed, so the
invoice_status in your first event for an invoice is frequently
draft. If you only care about issued invoices, filter on invoice_status or
subscribe to invoice.status_changed instead.
invoice.updated requires a tracked-field change. An update only emits an
event when one of these fields actually changed value:
amountdue_dateinvoice_datedescriptionnotes
invoice.updated event. Do not
rely on this event as a general “something about this invoice changed” signal.
Status-change events carry the old value. invoice.status_changed,
invoice_payment.status_changed, and credit_memo.status_changed include a
data.previous_attributes object holding the prior status
(invoice_status, payment_status, and memo_status respectively). It is only
present when the status genuinely changed.
Payload Envelope
Every delivery uses the same envelope, regardless of event type:
The request body is serialized with sorted keys and no whitespace before signing,
so field order on the wire is alphabetical.
Example: invoice.status_changed
Example: invoice.created
A *.created event has no previous_attributes key at all:
Webhook Headers
The two signature headers are sent only when a signing secret is configured
on the webhook endpoint. Endpoints without a secret receive only
Content-Type.Signature Verification
The signed input is the timestamp and the raw body joined by a literal period:{timestamp}.{body}. The key is your endpoint’s signing secret.
Verify against the raw request body bytes exactly as received — we sign the
compact, key-sorted JSON we transmit, so re-serializing a parsed object will
produce a different string and the signature will not match.
Delivery and Retries
- Webhooks are delivered asynchronously on a background queue, after the originating database transaction commits.
- Each subscribed endpoint is dispatched independently. A failure on one endpoint does not prevent delivery to the others, and a retry never re-sends to an endpoint that already accepted the event.
- Up to 3 retries, at a fixed 60-second delay between attempts (not exponential backoff).
- Connection timeout is 5 seconds; response timeout is 30 seconds. Return a 2xx within 30 seconds or the attempt is treated as failed.
- Every attempt is logged, including the request body, request headers, response status code, and response body. These are visible per endpoint in the dashboard and are the fastest way to diagnose an integration.
- The event
idis stable across retries, so it is safe to use for idempotency/deduplication.
Which failures are retried
Best Practices
- Match on the delivered
type— thelowercase.dottedvalue, never theUPPER_SNAKEsubscription identifier - Respond quickly — return a 2xx status code within 30 seconds
- Process asynchronously — acknowledge immediately, then queue the event for background processing
- Verify signatures — validate
X-JustPaid-Signatureagainst the raw body bytes - Handle duplicates — use the event
idfor idempotency; it is stable across retries - Log unmatched event types — the surest way to catch a naming mismatch before it becomes silent data loss
- Handle out-of-order delivery — events are delivered in order when possible, but independent dispatch and retries mean ordering is not guaranteed
