Webhooks
Get a signed request when a GenFashion try-on finishes, instead of asking for the result repeatedly.
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.
- Endpoint URL: it must use
httpsand cannot be a private or loopback address. - Events: choose
generation.completed,generation.failed, or both. - 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#
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{
"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.
// 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.
| Attempt | When |
|---|---|
| 1 | Immediately |
| 2 | 30 seconds later |
| 3 | 2 minutes later |
| 4 | 10 minutes later |
| 5 | 1 hour later |
| 6 | 6 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.