Webhooks

Updated 7 November 2026

GenFashion can send a signed POST to your server when a try-on started through the Try-On API finishes. You can have more than one endpoint, for example one for staging and one for production.

Set up an endpoint#

Open Settings > Webhooks in GenFashion. You need to be an owner or admin.

  1. Endpoint URL: it must use https and cannot be a private or loopback address.
  2. Events: choose generation.completed, generation.failed, or both.
  3. Signing secret: one is created when you save. You can reveal it again on the same screen.

Send test makes a real delivery with "test": true in the body, so a pass means the whole path works.

What you receive#

code
POST https://shop.example.com/webhooks/try-on
Content-Type: application/json
User-Agent: GenFashion-Webhooks/1
X-Webhook-Signature: sha256=<hex>
X-Webhook-Event: generation.completed
X-Webhook-Delivery: whd_8f1c0a2b
X-Webhook-Attempt: 1
json
{
  "id": "whd_8f1c0a2b",
  "type": "generation.completed",
  "timestamp": "2026-09-13T09:41:02.113Z",
  "accountId": "acc_636028ff",
  "data": {
    "generationId": "gen_3f9a1c2b7d4e8a10",
    "status": "COMPLETED",
    "productExternalId": "shirt-42",
    "shopperRef": null,
    "externalUserId": "your-customer-id",
    "durationMs": 45113,
    "creditsCharged": 1
  }
}

A retry sends the same body, so use id to ignore repeats. A failed try-on has status: "FAILED" and an errorCode. See Errors.

Verify the signature#

X-Webhook-Signature is sha256= followed by the hex HMAC-SHA-256 of the raw request body, using your signing secret.

Hash the bytes you received. If your framework parses the JSON first, the bytes change and the signature will not match.

js
// Express: use express.raw, not express.json
app.post("/webhooks/try-on", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.GENFASHION_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(req.get("X-Webhook-Signature") ?? "");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString("utf8"));
  // Ignore event.id if you have seen it, store the event, then reply.
  res.sendStatus(200);
});

The timestamp is inside the signed body, so it cannot be changed without breaking the signature.

Retries#

Anything other than a 2xx response is retried, including a 4xx.

AttemptWhen
1Immediately
230 seconds later
32 minutes later
410 minutes later
51 hour later
66 hours later

After the sixth attempt, the delivery shows as failed in the delivery log, which lists each attempt's response code and duration.

Reply with a 2xx as soon as you have stored the event, and do the rest of your work afterwards.

Volume#

There is no cap on deliveries. You get one webhook per finished try-on, sent one at a time to each endpoint.

Rotate the secret#

Rotate creates a new secret and the old one stops working at once. Deploy the new secret on your side first, or expect a short gap when verification fails.

New to GenFashion? See the product overview or start with free credits.