Docs/Webhooks

Webhooks

When using delivery_mode: webhook, Apertur POSTs images to your endpoint as they are processed.

Request headers

HeaderDescription
X-Aptr-SignatureHMAC-SHA256 signature of the request body
X-Aptr-Session-IdThe upload session ID
X-Aptr-Image-Index0-based index of this image within the session
X-Aptr-Image-IdUnique image ID
Content-TypeDepends 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=&lt;hex-digest&gt; 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:

AttemptDelay after previous
1st retry30 seconds
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry6 hours

Additional retries continue until the plan’s max retry days are exhausted. When all retries fail, you receive a failure notification email.