Skip to content

How to Use Webhooks

Receive an HTTP callback when a task finishes, instead of polling for it.

Overview

Pass a callback URL when you submit a task and Get3W will POST the result to it once the task reaches a terminal state (completed or failed). This is the better choice for long-running jobs like video generation, where polling wastes requests.

Delivery runs in a background thread on the worker, so it never blocks task execution.

Setup

The callback URL goes in the webhook query parameter, not the request body:

bash
curl -X POST "https://api.get3w.com/v1/google/nano-banana-pro/text-to-image?webhook=https://your-server.com/callback" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A cat in space",
    "aspect_ratio": "16:9"
  }'

Remember to URL-encode the callback URL if it contains query parameters of its own.

The submit call returns immediately:

json
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "status": "created",
  "estimated_duration": 52
}

Webhook Payload

When the task finishes, Get3W POSTs this JSON body to your URL:

json
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "model": "google/nano-banana-pro/text-to-image",
  "status": "completed",
  "code": 0,
  "created_at": "2026-03-28T07:50:42",
  "input": {
    "prompt": "A cat in space",
    "aspect_ratio": "16:9"
  },
  "outputs": ["https://storage.example.com/output.png"],
  "error": null
}
FieldTypeDescription
idstringTask ID, same as the submit response
modelstringModel slug
statusstringcompleted or failed
codeint0 on success; see Error Codes on failure
created_atstringWhen the task was created
inputobjectThe parameters you submitted
outputsstring[]Output URLs; empty when the task failed
errorstringError message; null when the task succeeded

On failure the shape is the same, with outputs empty and error populated:

json
{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "model": "google/nano-banana-pro/text-to-image",
  "status": "failed",
  "code": 1200,
  "created_at": "2026-03-28T07:50:42",
  "input": { "prompt": "..." },
  "outputs": [],
  "error": "Content moderation: prompt contains prohibited content"
}

Because every delivery carries the same keys, you can branch on status without checking for missing fields.

Handling Webhooks

Python (Flask)

python
from flask import Flask, request

app = Flask(__name__)

@app.route("/callback", methods=["POST"])
def handle_webhook():
    data = request.json

    if data["status"] == "completed":
        print(f"Task {data['id']} completed: {data['outputs']}")
    elif data["status"] == "failed":
        print(f"Task {data['id']} failed ({data['code']}): {data['error']}")

    return "", 200

Node.js (Express)

javascript
const express = require("express");
const app = express();
app.use(express.json());

app.post("/callback", (req, res) => {
    const { id, status, outputs, code, error } = req.body;

    if (status === "completed") {
        console.log(`Task ${id} completed:`, outputs);
    } else if (status === "failed") {
        console.log(`Task ${id} failed (${code}):`, error);
    }

    res.sendStatus(200);
});

app.listen(3000);

Requirements

  • Your endpoint must be publicly reachable
  • It must accept a POST with a JSON body
  • It must return a 2xx within 30 seconds, or the delivery counts as failed
  • HTTPS is recommended

For local development, expose your server with a tunneling tool such as ngrok.

Retry Policy

If your endpoint returns a non-2xx status or is unreachable, Get3W retries with exponential backoff:

AttemptDelay before attempt
1stImmediate
2nd2 seconds
3rd4 seconds

After 3 failed attempts the delivery is abandoned. The result is not lost — you can still fetch it with GET /v1/requests/{request_id} for as long as the task record is retained.

TIP

Treat webhooks as a fast path, not the only path. A short reconciliation poll for tasks you never got a callback for makes the integration resilient to your own downtime.

Handling Duplicates

A retry can arrive after your server already processed the first delivery but failed to respond in time. Make your handler idempotent by keying on the task id and ignoring a delivery you have already seen.

Next Steps

Released under the MIT License.