# Ayrshare Webhooks

Ayrshare is a social media posting and management API. It POSTs JSON to a URL you
register per **action** (scheduled posts, social account linking, messages,
comments, ads, ...) on your Primary Profile or a User Profile. Deliveries are
signed with HMAC-SHA256 when you set a signing secret.

> Register a webhook secret with Ayrshare. Without one, deliveries are unsigned and fail verification.

## When to Use This Skill

* How do I receive Ayrshare webhooks?
* How do I verify the Ayrshare `X-Authorization-Content-SHA256-V2` signature?
* Why does my Ayrshare webhook signature fail after rotating the secret?
* How do I handle Ayrshare `scheduled`, `social`, `messages` or `comments` actions?
* How do I deduplicate Ayrshare webhook retries (`hookId`)?

## Verification (core)

Ayrshare sends `X-Authorization-Content-SHA256-V2: v1=<hex>`, the lowercase hex
HMAC-SHA256 of the **raw request body** keyed with your signing secret (UTF-8
bytes, used as-is). For 24 hours after a rotation the header carries two entries,
`v1=<current>,v1=<previous>`: accept the delivery if any `v1=` entry matches. The
timestamp header (`X-Authorization-Timestamp`) is not signed, so there is no replay
window to check.

```javascript
const crypto = require('crypto');
const HEX_SHA256 = /^[0-9a-fA-F]{64}$/;

function verifyAyrshareSignature(rawBody /* Buffer */, header, secret) {
  if (!header || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  let matched = false;
  for (const part of header.split(',')) {
    let candidate = part.trim();
    if (candidate.startsWith('v1=')) candidate = candidate.slice(3);
    else if (candidate.includes('=')) continue;       // other key: ignore
    if (!HEX_SHA256.test(candidate)) continue;        // validate before decoding
    if (crypto.timingSafeEqual(Buffer.from(candidate, 'hex'), expected)) matched = true;
  }
  return matched;
}

```

Return `401` for a missing, empty, malformed or non-matching header. Parse the JSON
only after the check, and treat every field of the parsed body as untrusted
(check types before reading into nested objects).

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ayrshare-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ayrshare-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ayrshare-webhooks/examples/fastapi/).

Hookdeck's Ayrshare source (`AYRSHARE`) verifies the same header: HMAC SHA-256,
hex, `x-authorization-content-sha256-v2`.

## Common Event Types

Dispatch on the body's top-level `action`. `subAction` refines `messages`,
`comments`, `mentions` and `scheduled`; `automations` and `ads` carry a `type`.

| `action` | Detail | Triggered when |
| --- | --- | --- |
| `scheduled` | `status`, `postIds`, `errors`; `subAction: tikTokPublished` | A scheduled post was processed (success or error); TikTok finished publishing |
| `social` | `type`: `link`, `unlink`, `refresh` | A social account was linked, unlinked or needs refreshing |
| `messages` | `subAction`: `messageCreated`, `messageEdited`, `messageRead`, `reactionCreated` | A direct message was received, edited, read or reacted to |
| `comments` | `subAction: comment` | A comment was created on connected content |
| `mentions` | `subAction: mention` | Your Instagram account was mentioned |
| `batch` | `batchType`, `url`, `userCount` | A batch export of users is ready |
| `feed` | `data` | A new RSS feed item was found |
| `automations` | `type`: `automation.triggered`, `automation.completed` | An automation fired or finished |
| `ads` | `type`: `created`, `spendSynced`, `accountConnected`, `accountDisconnected`, `accountRevoked` | An ad or ad account changed |
| `demo` | `type`: `upgradeWarning`, `upgrade` | Demo plan limits |
| `suspended` | `type`: `suspend`, `unsuspend` | Profile suspended or reinstated |

The examples handle `scheduled`, `social`, `messages`, `comments` and `ads`, and
log the rest.

## Delivery and Idempotency

Answer `200` within 15 seconds. `408`, `425`, `429`, `5xx`, timeouts and dropped
connections are retried (up to 9 attempts over about an hour, then roughly 5 min,
30 min, 2 h and 12 h later); other `4xx` responses are final. Delivery is at least
once: dedupe on the body's `hookId` and hold claims for 24 hours or more. A retry
rebuilds the payload, so the bytes (and signature) can differ between attempts.

## Environment Variables

```bash
AYRSHARE_WEBHOOK_SECRET=your_ayrshare_signing_secret

```

One secret per User Profile, shared by every action that profile registers. The
examples return `500` if it is not set.

## Local Development

For local webhook testing, run the Hookdeck CLI via `npx` (no install required):

```bash
npx hookdeck-cli listen 3000 ayrshare --path /webhooks/ayrshare

```

No account required. The CLI provides a public URL that tunnels to your local
server, plus a web UI for inspecting requests.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ayrshare-webhooks/references/overview.md) - Ayrshare webhook concepts, actions, payload fields
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ayrshare-webhooks/references/setup.md) - Register webhooks and set the signing secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/ayrshare-webhooks/references/verification.md) - Signature details, rotation, debugging
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/ayrshare-webhooks/examples/) - Express, Next.js, and FastAPI handlers with tests