Skip to main content
NanoGPT can send a signed 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.
There are two kinds of webhook: 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 with whsec_ 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.
A callback_url must:
  • be an https URL of at most 2,048 characters
  • have no username, password or #fragment
  • use a host that resolves only to public addresses
The URL is checked when you send the request and again before every delivery attempt. It’s saved in normalized form, and it can’t be changed once the job has started. Don’t put secrets in the URL, because it’s stored as it is. Check the signature instead.

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.failed event 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 as callback_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 PUT creates the team’s secret and returns it once. Later PUT calls keep the secret and leave secret out 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 POST before any URL is set returns 404.
Errors use the team API format, { "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}.
A video job ID is enough to read that job’s status and output URLs. Keep webhook bodies and any logs of them private.

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.removed for each member and team.status.changed with deleted.
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 a POST 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

  1. Remove the whsec_ prefix from your secret, then base64-decode the rest to get the key.
  2. Build the signed content: {webhook-id}.{webhook-timestamp}.{raw body}.
  3. Compute HMAC-SHA256 of that content with the key, and base64-encode the result.
  4. Split webhook-signature on spaces. Each entry looks like v1,<signature>. Accept the request if any v1 signature matches, using a constant-time comparison.
  5. Reject the request if webhook-timestamp is too far from the current time.
The timestamp check limits how long a captured request can be replayed. To block replays within that window too, keep the 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:
  1. Generate the new secret. The old secret keeps signing for up to 24 hours.
  2. During that time, each request carries a signature from both, so your endpoint can verify with either one.
  3. Update your endpoint to the new secret.
NanoGPT keeps the five most recent replaced secrets. If you rotate more than five times in 24 hours, the oldest stops signing early. When accounts are merged, jobs started on the merged account are signed with its own secret and the surviving account’s secret.

Retries

An attempt succeeds when your endpoint returns any 2xx 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.
A host that can’t be found is retried on the normal schedule. Events can arrive more than once and in any order. Use 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_url return 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, so callback_url doesn’t apply to them. Video webhooks can be used with it, and the record above is still kept.