The
error column says which. Every code is listed under
Error codes.
Nothing is arriving
The event is not one Custral emits
The event is not one Custral emits
The catalog is the complete list of the
nineteen events Custral delivers. An event that is not on it never fires, so
there is nothing to wait for.
The change came from a bulk write
The change came from a bulk write
Imports, job runs and AI property fills are suppressed so one import does
not become ten thousand deliveries. The record really did change and no
webhook was sent. See
Bulk writes.
The subscription does not match
The subscription does not match
Matching is exact,
prefix.*, or *. record.update does not match
record.updated, and records.* does not match anything. Read the event
list back from the endpoint’s row rather than from memory.The endpoint is inactive, or was deleted
The endpoint is inactive, or was deleted
An inactive endpoint records its delivery as failed with
webhook_inactive_or_missing and does not retry.Custral refused the URL
Custral refused the URL
The log shows
unsafe_url: and a reason. The URL is re-checked on every
attempt, not only when it was created, so a URL edited to something internal
stops working immediately. The reasons are listed under
What Custral will and will not call.unsafe_url:resolves_to_private_ip means the hostname looks public but its
DNS answers with a private address, such as an internal load balancer or a
record left over from a staging setup. Every address the hostname resolves
to has to be public, so one private answer among several is enough to
refuse it.Your endpoint redirects
Your endpoint redirects
The log shows
redirect_not_followed, with the 3xx in responseStatus.
Custral does not follow redirects, so the delivery never reaches the address
your server redirects to. The common causes are an http:// URL on a server
that forces https://, a trailing slash your framework adds or strips, and
an apex domain that redirects to www.Register the URL your endpoint redirects to. A redirect is retried like any
other failure, so a deploy that briefly answers with one recovers on its
own. See Redirects are not followed.Your endpoint was down
Your endpoint was down
Deliveries that failed during the outage are retried on the
retry schedule:
up to 8 attempts over about 24 hours, with waits that grow from 5
seconds to 18 hours. So an event can arrive hours after your endpoint is
back online. A
failed row with attempts below 8 is still waiting for
its next try, unless its error is webhook_inactive_or_missing or starts
with unsafe_url:.If all 8 attempts fail, the delivery is not tried again and there is no way
to replay it by hand. Reconcile from the API
after an outage that lasted longer than about a day.Signature verification keeps failing
Your framework parsed the body
Your framework parsed the body
The most common cause by a distance. Verification needs the exact bytes that
were signed, and re-serializing a parsed object does not reproduce them.
Mount a raw body parser on the webhook route only:The SDK throws
invalid_payload with this exact advice when it is handed an
object.The secret is from a different endpoint
The secret is from a different endpoint
Each endpoint has its own secret. Two endpoints pointing at one URL need the
right secret per subscription, or verification fails for one of them.
The secret was rotated
The secret was rotated
Rotation is immediate and has no overlap window. Deliveries signed with the
new secret fail against the old one from the next event onward.
Your clock is off
Your clock is off
The default tolerance is 300 seconds either side. A container with a drifted
clock rejects perfectly good deliveries. Compare your host’s time against
the
t value in the header.You are comparing with ===
You are comparing with ===
Fix the timing leak while you are there, but note this does not usually
cause a false negative. If a constant-time compare fails, the bytes really
do differ.
The same event arrives twice
Expected, and your handler has to tolerate it. The usual cause is a timeout: your server did the work, the response took more than 10 seconds, Custral never saw it and retried. Deduplicate on the envelope’sid, which is stable across retries of one
delivery. createdAt is not: it is stamped per attempt. See
Your handler must be idempotent.
Events arrive out of order
Also expected. Deliveries are processed concurrently. If order matters, treat the payload as a nudge and read current state back from the API.A value looks like gibberish
["opt_3Kd8sM"] is a select’s stored value, not a bug. Select, status, relation
and user fields carry ids. Resolve them from the object’s schema, as shown in
Payload format.
Still stuck
Quote the delivery id (whd_…) and the endpoint id (hook_…) when you write
in. Both are on the delivery log row, and together they identify one specific
attempt end to end.
Get support
Or email hello@custral.com.