POST request to your HTTPS endpoint when a batch or video job finishes, and when a team’s members, usage or status change. Webhooks follow the Standard Webhooks format, so you can verify them with its libraries.
Webhooks are available for nano-gpt.com accounts. Partner domains don’t support them.
Webhooks tell you that something happened. They don’t carry results: fetch those with your API key, as you would without a webhook. A delivery can fail or repeat, and there’s no API to list or replay events, so keep polling the status endpoints as a fallback and treat them as the source of truth.
Job webhooks
1. Generate a signing secret
Open Settings, find Webhook signing secret, and choose Generate secret. The secret starts withwhsec_ and is shown once, so copy it then.
A request with a callback_url is refused until your account has a secret.
The same action is available at /api/user/webhook-secret. It accepts a signed-in browser session only, so an API key can’t read or replace the secret.
2. Send a callback_url
Add callback_url to the request body:
Anthropic-format Message Batches at
/api/v1/messages/batches don’t support callback_url.
callback_url must:
- be an
httpsURL of at most 2,048 characters - have no username, password or
#fragment - use a host that resolves only to public addresses
Errors
The
param field is callback_url on all of these. Batch errors use the { "error": { "message", "type", "code", "param" } } format. Video errors use { "error": "...", "code", "param" }.
When a job event is sent
Each job sends at most one event, when it reaches a final status.- A batch event can be held for up to 15 minutes after the batch finishes, while billing settles. Delivery can add further delay.
- If your create request returned an error, its webhook is normally removed, because you have no job ID to match an event to. An interrupted request can still leave one behind, so ignore events for job IDs you don’t recognize.
- A job that still hasn’t finished after about eight days stops being tracked, and no event is sent.
- Each video job creates at most one final event, and its delivery can fail or repeat. A rare later change to the job doesn’t create another event, so the status endpoint stays authoritative.
- A
video.generation.failedevent can arrive before any refund has landed.
Team webhooks
Team owners and admins can set one HTTPS URL per team under Teams, then Settings, then Webhook. The URL follows the same rules ascallback_url.
The same actions are available at /api/teams/{teamUuid}/webhook. It accepts a signed-in browser session only, so an API key can’t read or change a team’s webhook.
- The first
PUTcreates the team’s secret and returns it once. LaterPUTcalls keep the secret and leavesecretout of the response. - Changing the URL moves undelivered events to the new URL.
- Removing the webhook cancels undelivered events.
- An attempt already in progress can still reach the old URL after either change.
- A suspended team can read or remove its webhook, but not change it.
- A
POSTbefore any URL is set returns 404.
{ "code", "message" }. A URL that fails validation returns INVALID_CALLBACK_URL. Someone without permission gets 403, and someone outside the team gets 404. If your sign-in changes while the request is in progress, the response is 409 with SESSION_CHANGED.
Anyone who has seen the team’s secret can sign events. Replace it when someone with access leaves.
Events
Every event has the same shape:
Job events carry IDs and status. Team events carry IDs and, depending on the event, a role, status or period. No event contains prompts, outputs or file contents. Parse them so that fields added later don’t break your handler.
Batch events
data.id is the batch ID. Use it with GET /api/v1/batches/{id}, and use the API key that created the batch to read its status and files.
Video events
data.id is the job’s runId (vid_...). Use it with GET /api/video/status?requestId={id}.
Team events
- Role changes don’t send an event.
- When two memberships merge into one, the one that ends sends
team.member.removed. - Deleting a team sends
team.member.removedfor each member andteam.status.changedwithdeleted.
team.usage.limit_reached needs an active team with an enforced team limit, and it’s sent at most once per team per month. Spend is normally checked about every five minutes, using settled usage, and it can take longer when many teams are due. So requests can be refused before the event arrives, because enforcement also counts spend still in progress. A limit reached in the last minutes of a month may never send an event. Member limits don’t send it.
The request
Each delivery is aPOST with a JSON body and these headers:
Verifying a webhook
Verify every request before you act on it. Use the raw request body: parsing and re-serializing the JSON changes the bytes and the signature won’t match. The Standard Webhooks libraries check the signature and the timestamp for you, and reject a timestamp more than five minutes from the current time.Verifying without a library
- Remove the
whsec_prefix from your secret, then base64-decode the rest to get the key. - Build the signed content:
{webhook-id}.{webhook-timestamp}.{raw body}. - Compute HMAC-SHA256 of that content with the key, and base64-encode the result.
- Split
webhook-signatureon spaces. Each entry looks likev1,<signature>. Accept the request if anyv1signature matches, using a constant-time comparison. - Reject the request if
webhook-timestampis too far from the current time.
webhook-id values you’ve processed and ignore repeats.
Rotating a secret
Generating a new secret doesn’t cut off the old one straight away:- Generate the new secret. The old secret keeps signing for up to 24 hours.
- During that time, each request carries a signature from both, so your endpoint can verify with either one.
- Update your endpoint to the new secret.
Retries
An attempt succeeds when your endpoint returns any2xx status within 10 seconds. That time includes connecting, and your response body is ignored. A 2xx ends the retries, so finish the work or save the event somewhere durable before you respond, and leave anything slow until after.
Anything else counts as a failure, including a timeout or a redirect. Redirects aren’t followed.
After a failed attempt, NanoGPT tries again after these delays:
That’s nine attempts over about two days. These delays are minimums: attempts run from a job that starts once a minute, and they can take longer when many events are waiting. Every attempt has the same
webhook-id and body, with a new timestamp and signature. After the last attempt, the event is dropped.
Retries stop early in these cases:
- Your endpoint returns
410 Gone. This fails that event and every other pending event from the same account or team for that URL, including ones not yet attempted. It doesn’t disable the URL: events that become ready later are still sent. - The URL now points somewhere that isn’t allowed, such as a private address.
- The signing secret no longer exists, for example because the team’s webhook was removed.
webhook-id to ignore repeats, and don’t rely on delivery order.
Limits
- When an account has 2,000 unfinished webhooks, counting jobs still running and events still being delivered, new requests with a
callback_urlreturn 429 until some finish. Requests sent at the same moment can each pass the check and go over that number. - A team can create up to 500 events per hour. Events over the limit aren’t sent, though the change on the team still happens. A single bulk change, such as deleting a team, can go past the limit.
- Deliveries are shared between accounts and destination hosts, so a large backlog for one account or host can take a while to clear.
Data and retention
A webhook record holds the callback URL, job or team IDs, the event type and body, and the delivery state. It doesn’t hold prompts or outputs. Finished records are removed by periodic cleanup, not straight away. Batches aren’t available with zero data retention, socallback_url doesn’t apply to them. Video webhooks can be used with it, and the record above is still kept.