Webhooks
When using delivery_mode: webhook, Apertur POSTs images to your endpoint as they are processed.
Request headers
| Header | Description |
|---|---|
| X-Aptr-Signature | HMAC-SHA256 signature of the request body |
| X-Aptr-Session-Id | The upload session ID |
| X-Aptr-Image-Index | 0-based index of this image within the session |
| X-Aptr-Image-Id | Unique image ID |
| Content-Type | Depends on format: multipart/form-data, application/json, or application/octet-stream |
Webhook formats
multipart(default)
The image is sent as a multipart/form-data POST. The file field name is image. Session tags are included as additional form fields.
Content-Type: multipart/form-data; boundary=----AptrBoundary ------AptrBoundary Content-Disposition: form-data; name="image"; filename="photo.jpg" Content-Type: image/jpeg <binary image data> ------AptrBoundary Content-Disposition: form-data; name="user_id" usr_abc123 ------AptrBoundary--
json_base64
The image is base64-encoded and included in a JSON body alongside metadata and tags.
{
"session_id": "sess_01HX...",
"image_id": "img_01HX...",
"image_index": 0,
"mime_type": "image/jpeg",
"filename": "photo.jpg",
"size_bytes": 245000,
"data": "base64-encoded-image-data...",
"tags": {
"user_id": "usr_abc123"
}
}binary
The raw image bytes are sent as the request body with Content-Type: application/octet-stream. Metadata is only available in headers.
HMAC signature verification
Every webhook request includes an X-Aptr-Signature header. The value is sha256=<hex-digest> computed over the raw request body using your project’s webhook secret.
Find your webhook secret in Dashboard → Project → Settings.
const crypto = require("crypto");
function verifySignature(body, signatureHeader, secret) {
const expected = "sha256=" +
crypto.createHmac("sha256", secret).update(body).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "utf8"),
Buffer.from(signatureHeader, "utf8")
);
}
// Express.js example
app.post("/webhook", express.raw({ type: "*/*" }), (req, res) => {
const sig = req.headers["x-aptr-signature"];
if (!verifySignature(req.body, sig, process.env.APTR_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
// Process image...
res.status(200).end();
});Security note
Always read the raw request body before parsing. Parsing first (e.g., with a JSON middleware) may alter the body bytes and cause signature verification to fail.
Retry policy
A webhook delivery is considered failed if your server returns a non-2xx status code or does not respond within 30 seconds. Failed deliveries are retried with exponential backoff:
| Attempt | Delay after previous |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 6 hours |
Additional retries continue until the plan’s max retry days are exhausted. When all retries fail, you receive a failure notification email.