Theme
Language

Webhooks and the media job lifecycle

Register a mediaWebhookUrl, receive a POST when a job finishes, poll GET /api/media/jobs/{id}, handle error codes, and understand charging and idempotency limits.

Operational guide to asynchronous media jobs: webhook registration, statuses, polling, error codes, charges and idempotency limits — strictly per the current implementation in the code.

Job lifecycle

  1. POST /api/media/generate — on success 202 and { ok: true, jobId, status: "processing" }. There are no separate queued / running statuses in the API: until the job finishes, the client sees processing.
  2. Upstream may go through internal stages (waiting, generating) — these are not reflected in the public API; while polling, the job stays processing.
  3. Terminal statuses: success (has resultUrls, charged on success) or fail (no charge).
  4. Completion is recorded once: via the upstream callback or on a lazy poll in GET /api/media/jobs/{id}.

Polling: GET /api/media/jobs/{id}

GEThttps://getvibeapi.com/api/media/jobs/{id}
ParameterTypeDescription
statusstringsubmitting | processing | success | fail. submitting + media_submission_uncertain is non-terminal; within about 10–15 minutes such a submission is closed as fail (media_submission_not_started) without a charge — create a new task.
resultUrlsstring[]Signed URLs /api/media/file/{id}/{idx}?sig=… (no API key).
priceRubnumberCharge amount in USD (0 on fail or before completion).
failMsgstring | nullSafe message on fail or a non-terminal media_submission_uncertain.
errorCodestring | nullError code (media_generation_failed) or non-terminal media_submission_uncertain.
retryGuidancestring | nullOn media_submission_uncertain: a safe instruction to poll the same jobId later and not create a duplicate automatically.
# 2. poll the job until it is ready
curl https://getvibeapi.com/api/media/jobs/JOB_ID \
  -H "Authorization: Bearer $GETVIBE_API_KEY"
# → {"status":"success","resultUrls":["https://..."],"priceRub":...}
Recommended polling interval: 3–5 s, with an exponential backoff up to 30 s. The client-side timeout is per your application SLA (video generation can take minutes).

Webhook registration

GEThttps://getvibeapi.com/api/webhooks
PATCHhttps://getvibeapi.com/api/webhooks

One URL per account: mediaWebhookUrl. Configured in dashboard → Webhooks or via the API with an account session (a cookie after login). The endpoint does not accept sk-pv-… — only an authenticated session.

# Example (a dashboard session is required, not an API key)
curl -X PATCH https://getvibeapi.com/api/webhooks \
  -H "Content-Type: application/json" \
  -b "session=YOUR_SESSION" \
  -d '{"mediaWebhookUrl":"https://example.com/webhooks/media"}'

# Clear the URL
curl -X PATCH https://getvibeapi.com/api/webhooks \
  -H "Content-Type: application/json" \
  -b "session=YOUR_SESSION" \
  -d '{"mediaWebhookUrl":null}'
URL requirements
ParameterTypeDescription
mediaWebhookUrlHTTPS URLA public HTTPS address; private/loopback addresses and our own domains are refused (SSRF protection), and the address is re-checked before every delivery. An empty string or null resets it.
balanceWebhookUrlHTTPS URLA separate webhook for low balance (not media), signed with the same account secret, x-pv-event: balance.low. The threshold is balanceWebhookThresholdRub.

Webhook delivery on job completion

When a job reaches a terminal status, GetVibe creates one logical event and delivers it via POST to the registered mediaWebhookUrl. Delivery is capped: the service makes up to 5 attempts total. The same payload may arrive more than once.

// POST to your mediaWebhookUrl when a job finishes
{
  "jobId": "...",
  "status": "success",            // or "fail"
  "model": "veo-3.1",
  "kind": "video",                // image | video | audio | music | 3d
  "resultUrls": ["https://getvibeapi.com/api/media/file/..."],
  "failMsg": null,
  "createdAt": "2026-06-17T10:00:00.000Z"
}
Delivery behavior (as in code)
ParameterTypeDescription
Signature / auth—Every delivery is signed with Standard Webhooks: the webhook-id, webhook-timestamp and webhook-signature headers (v1, base64 HMAC-SHA256 of “id.timestamp.body”). The account secret is shown in the dashboard → Webhooks and returned to an API key as accountSecret by GET /api/media/webhook-secret; deliveries to a per-request webhook_url are signed with that key's secret (the secret field). Reject a request with a wrong signature or a timestamp older than 5 minutes.
RetriesoutboxUp to 5 attempts total; interval after a failure: 1, 2, 4, 8 minutes. After exhausting attempts, the event moves to FAILED and is no longer delivered automatically. Polling GET /api/media/jobs/{id} remains the source of state.
Receiver deduprequiredDeduplicate by the jobId + status pair and make your handler idempotent. Do not use the webhook as the sole source of state.
Content-Typeapplication/jsonBody is JSON with fields jobId, status, model, kind, resultUrls, failMsg, createdAt.

POST /api/media/generate errors

HTTPWhenChargeRetry
202Job accepted (ok: true, jobId)No (only after success)—
400Invalid JSON / request bodyNoFix the request
401Missing or invalid API keyNoCheck Authorization
402Insufficient balance (admission)NoTop up the balance
422Unknown model, invalid opts, ok: false from facadeNoFix model/opts
502media_submit_failed — upstream unavailableNoRetry with backoff
503priceUnavailable, routing guard, VIDEO_DISABLED, veo without priceNoAnother model / later

Polling errors and terminal failures

HTTP / statusMeaningCharge
404job not found or a foreign jobIdNo
processingThe job is still runningNo
successDone, resultUrls populatedYes — priceRub in the response
failGeneration failedNo (priceRub = 0)

Billing and idempotency

  • Charged once on success. In finalizeJob the transition processing → success|fail is atomic: a repeated callback or poll does not create a second charge.
  • fail — free. No debit is performed on a fail status.
  • Admission quote. If a quote was saved at creation, exactly it is charged on success (never more than the actual upstream metering).
  • Create idempotency — by header. A repeated POST to /api/media/generate without the header creates a new job and a new charge on success. Send Idempotency-Key (up to 255 ASCII characters): a retry with the same key and body within 24 hours returns the same jobId without a new job; the same key with a different body is 422, and 409 while the first request is still being processed. The jobId of a paid generation is also shown in GET /v1/generations (the job_id field).
  • Webhook is capped at five attempts. One logical event is created per terminal transition, but delivery attempts may repeat. After five failures the event gets the status FAILED . The client handler must deduplicate the pair jobId + status.
See also: Media generation, Media model catalog, Errors and limits (common 401/402/429 codes for /v1).
GetVibe API media webhooks — statuses, errors, billing | GetVibe API