Webhooks
Webhooks are turned on per account. Where they are not on for yours, the Platform has
no webhook endpoints page and nothing is sent. Polling GET /v1/jobs/{job_id} works
everywhere and stays the contract.
A webhook is additive: it delivers the same Job that polling returns, at the moment the job reaches a terminal state, so you can stop polling once you have it. It is never the only way to learn a result.
You register endpoints on the Webhooks page of the Platform, per environment (live or test). Each endpoint has an HTTPS URL on port 443, the event types it subscribes to, and a signing secret that is shown once, when you create or rotate it.
Events
A job emits exactly one terminal event.
The envelope
data.objectis the Job exactly asGET /v1/jobs/{job_id}returns it. Output assets arehrefplusexpires_at, as on the poll; after expiry thehrefanswers 404.createdis ISO-8601 UTC, like every V1 timestamp.livemodeis present only where test-mode keys are available, and matches the Job’s environment.- For
test.ping,data.objectis{"object": "test", "endpoint_id": "..."}. - If the job is deleted before a delivery or resend goes out,
data.objectis{"id": "job_...", "object": "job", "deleted": true}and nothing else. - The body carries no input image, no email, no account identifier, and no presigned URL.
The request
Verify every delivery
The signature covers the timestamp and the raw body:
A verifier must:
- read
texactly once and at least onev1from the header, ignoring any other scheme; - refuse a
tmore than 300 seconds from its own clock, in either direction; - accept the delivery if any
v1matches, comparing in constant time; - parse the JSON only after the signature verified.
Hand the verifier the body bytes as received: a body that your framework parsed and re-serialized will not verify.
Without an SDK
The check needs only your language’s standard library, so it works with any SDK version or none:
During a rotation, call it once per secret you hold and accept if either is valid.
With an SDK
Both SDKs do exactly the checks above and return the parsed event.
tolerance_s / toleranceSeconds narrows the 300-second window if you want; now
lets a test pin the clock. The decoded Job is
morphiaone.v1.parse_job(event.data["object"]) in Python and
parseJob(event.data.object) in TypeScript.
Delivery guarantees
- At least once. A delivery can arrive more than once.
event.idis stable across retries and console resends, so dedupe on it. - No ordering. Events for different jobs can arrive in any order. Each job emits one terminal event, so ordering never matters within a job.
- Answer fast. Any
2xxwithin 10 seconds is a success. Anything else — a3xx(redirects are not followed), a4xx, a5xx, a timeout, a TLS or connection failure — is a failed attempt. Acknowledge first and process asynchronously. - Polling stays authoritative. If a webhook is late, missing or disagrees with
GET /v1/jobs/{job_id}, the poll is right.
Retries
A failed attempt is retried, up to 8 attempts in total. The waits before attempts 2
to 8 are about 1 minute, 5 minutes, 30 minutes, 1 hour, 3 hours, 6 hours and 12 hours,
each with up to 20% random jitter either way. No attempt is scheduled more than 24
hours after the event was created; a delivery that has not succeeded by then stops,
and the console shows it as exhausted. The MorphiaOne-Delivery-Attempt header
counts from 1.
Automatic disabling
An endpoint that has failed 20 attempts in a row and has been failing for at least 24 hours is disabled. Its pending deliveries are cancelled, and the console shows a banner. Re-enabling it is your action: it resets the failure count, and it does not revive cancelled deliveries — resend those from the delivery log.
Rotating the secret
Rotate on the endpoint’s page in the Platform. The new secret is shown once. You choose how long the
previous secret stays valid (up to 7 days); until then every delivery carries two
v1 values, one per secret, so a receiver holding either verifies. Deploy the new
secret, then let the old one expire. If you hold both on your side, pass both: the
SDKs accept a list.
Test events and the delivery log
“Send test event” sends one test.ping to the endpoint, signed like any other
delivery and attempted once. Every delivery is listed per endpoint with its
attempts, status codes and timings, and any delivery can be resent with the same
event.id.