
Webhooks when a job finishes
Stop polling and let Picverce AI tell you when a job is done. What arrives, how to verify it came from us, and the checks worth writing first.
Image EditingPolling works and it is the right way to start. Once you have more than a handful of jobs in flight, being told when one finishes is better than asking repeatedly.
That is all a webhook is here. You give an address, and a finished job gets posted to it.
What you set up and where
Endpoints are registered under the webhooks section of your account, not through the API itself. There is no call that creates one.
That is a deliberate limit rather than a missing feature. An address that receives your results is not something a leaked key should be able to change.
You supply a URL you control, and you get a signing secret in return. Keep that secret the same way you keep the key.
Only two things are ever sent, one for a job that succeeded and one for a job that failed.
Nothing arrives while a job is queued or still processing. If you want progress, polling is still the way to get it.
Two events is a small surface and that is the point. There is nothing to filter and no subscription model to configure.
What arrives at your endpoint
A POST with a body describing the event, and a set of headers that let you check it.
- An event header saying whether the job succeeded or failed.
- A delivery identifier unique to this attempt.
- A timestamp, as seconds.
- A signature, prefixed to show which scheme produced it.
The body contains the job in the same shape you would have got by polling for it. Nothing new to learn, and no second parser to write.
That symmetry is worth using. Write one function that handles a finished job and call it from both your poll loop and your endpoint.
It also makes moving between the two painless. Starting with polling and adding an endpoint later costs you almost nothing if the handler was already separate.
Verifying it actually came from us
This is the part to get right before you do anything with the contents, because a public URL can be posted to by anyone.
Compute a hash over the timestamp and the raw body using your signing secret, then compare it to the signature header.
Two details decide whether this works. Compare with a constant time function rather than a normal string equality, and hash the raw body exactly as received.
If you parse the body into an object and then serialise it again before hashing, the bytes change and nothing will ever match. That mistake costs people an afternoon.
Also reject anything with a timestamp older than five minutes. The signature itself does not expire, so that age check is what stops somebody replaying a delivery they captured earlier.
When I set this up the first time I skipped the age check and only added it after reading why it exists. It is two lines and it closes a real hole.
The checks worth writing on day one
A few habits make an endpoint that behaves under real conditions rather than only in testing.
- Answer quickly and do the work afterwards. A slow endpoint looks like a broken one.
- Expect the same delivery more than once and make handling it twice harmless.
- Handle the failed event, not just the successful one. Credits are already refunded, so your job is to decide whether to retry.
- Log the delivery identifier so you can trace a specific event later.
Duplicate deliveries are normal in any callback system. Keying your handler on the job id rather than assuming one message per job removes the whole class of problem.
Order is not guaranteed either. Two jobs finishing close together can arrive in either sequence, so never infer anything from which landed first.
A 2xx answer means delivered. Anything else is retried twice more, roughly two and eight seconds later, and a retry reuses the same delivery identifier.
Answering 404 or 410 stops the retries entirely, which is the polite way to retire an endpoint you no longer want deliveries on.
Keep the poll loop as a fallback for the first while. An endpoint that silently stops receiving is harder to notice than a loop that stops finishing.
A local tunnel is the easiest way to develop against this. Pointing the endpoint at your machine lets you read real deliveries instead of writing a fake one.
Log the raw body before you parse it, at least while building. Almost every signature problem is visible the moment you can see exactly what arrived.
Once it is working, keep logging the delivery identifier and drop the rest. Bodies contain your results and do not belong in a log forever.
The full header list and the exact signature construction are in the Public API documentation, which is worth reading once rather than guessing at.
And callbacks only pay off on repeating work. For a one off batch like an Etsy listing I cleaned before it went live, a poll loop is less to build and finishes the same day.
Verify, answer fast, expect duplicates, handle failure. Four rules and the endpoint stays boring.


