AppForShareSet up an integration

Docs

Webhooks

Slack and Discord get a ready-made message: the app icon, version, platform and size (or the device, OS and country for installs), release notes, and a button to the install page. Slack hooks are rate limited to about one message a second, so on a busy hook messages wait their turn instead of being dropped. A generic webhook gets the JSON below, signed so you can check it came from AppForShare. Integrations are part of Pro and Team.

Events

  • build.ready: A build finished processing and its install link works. Data: build.
  • build.failed: An upload could not be turned into a build. Data: upload (id, fileName, error).
  • build.installed: Someone started an install. Data: build, install (device, osVersion, model, country).
  • bandwidth.warning: Your monthly bandwidth reached 80% or 100%. Data: bandwidth (percent, usedBytes, capBytes, month).

The Send test button delivers a ping event with no data.

Request

POST /your/endpoint
Content-Type: application/json
X-AppForShare-Event: build.ready
X-AppForShare-Delivery: 1842
X-AppForShare-Signature: t=1791850000,v1=5f0c...e91a

{
  "id": "3b8e6c1e-9d7a-4c55-8f0e-2a3c5d1b7f42",
  "event": "build.ready",
  "createdAt": "2026-10-12T18:20:00.000Z",
  "data": {
    "build": {
      "id": 31245,
      "slug": "Ab3dE9",
      "name": "My App",
      "platform": "IOS",
      "bundleId": "com.example.myapp",
      "version": "2.4.0",
      "versionCode": "118",
      "sizeBytes": 48211968,
      "installUrl": "https://appforshare.io/i/Ab3dE9",
      "iconUrl": "https://appforshare.io/api/i/Ab3dE9/icon",
      "releaseNotes": "Fixes the login crash",
      "createdAt": "2026-10-12T18:19:58.000Z"
    }
  }
}

Checking the signature

The signature is HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the signing secret shown when you add the webhook.

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody = the request body exactly as received, before JSON parsing.
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const got = Buffer.from(parts.v1 ?? '', 'hex');
  return got.length === 32 && timingSafeEqual(got, Buffer.from(expected, 'hex'));
}

Delivery

  • Answer with any 2xx status within 10 seconds. Redirects are not followed.
  • Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.
  • A 404 or 410 answer stops retries for that delivery.
  • After 15 failures in a row the webhook is switched off and we email you.
  • A retry sends the same body with the same event id; use the id to skip duplicates.
  • Only https URLs on port 443 or 8443 that resolve to public addresses are accepted.