Developers

REST API & Webhooks

Build on top of IGMsg. Programmatically manage automations, query DM logs, and receive real-time event webhooks.

Authentication

Every API request must include a Bearer token in the Authorization header. Generate tokens at igmsg.com/developer. Tokens look like igmsg_ followed by 40 alphanumeric characters.

curl https://igmsg.com/api/v1/me \
  -H "Authorization: Bearer igmsg_YOUR_TOKEN_HERE"

The plaintext token is shown once at creation - store it in a secret manager (1Password, AWS Secrets Manager, your CI's secrets store). You cannot retrieve it again, but you can always revoke and re-issue.

Base URL & versioning

All endpoints live under https://igmsg.com/api/v1. We will only ever introduce additive changes within v1 - new fields, new endpoints. Removals or breaking changes will live in v2.

Rate limits

60 requests per minute per token. Responses include standard headers:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47

When exceeded, you'll get 429 Too Many Requests with a Retry-After header. Implement exponential backoff in your client.

Error format

Errors are JSON with an error code and human-readable message. HTTP status codes follow standard conventions (401 unauthenticated, 403 forbidden, 404 not found, 422 validation error, 429 rate-limited, 5xx server error).

{
  "error": "unauthorized",
  "message": "Invalid or expired API token."
}

Endpoints

GET /me - Current user

curl https://igmsg.com/api/v1/me \
  -H "Authorization: Bearer igmsg_YOUR_TOKEN"
{
  "id": 42,
  "name": "Manish Sharma",
  "email": "manish@example.com",
  "plan": "pro",
  "created_at": "2026-01-12T08:30:11+00:00"
}

GET /instagram-accounts - List connected IG accounts

{
  "data": [
    {
      "id": 7,
      "username": "yourbrand",
      "ig_user_id": "17841401234567890",
      "profile_picture_url": "https://...",
      "is_connected": true,
      "token_expires_at": "2026-07-15T12:00:00+00:00",
      "connected_at": "2026-04-02T09:14:33+00:00"
    }
  ]
}

GET /posts - List posts

Paginated. Query params: per_page (1-100, default 25), instagram_account_id (filter).

curl "https://igmsg.com/api/v1/posts?per_page=10&instagram_account_id=7" \
  -H "Authorization: Bearer igmsg_YOUR_TOKEN"

GET /automations - List automations

Paginated. Returns automation objects with their linked post.

POST /automations - Create automation

curl -X POST https://igmsg.com/api/v1/automations \
  -H "Authorization: Bearer igmsg_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "post_id": 123,
    "name": "Spring launch",
    "keywords": ["SHOP", "BUY"],
    "keyword_match_mode": "exact",
    "message_template": "Here's the link! {{link}}",
    "is_active": true
  }'

Returns 201 with the created automation. Required fields: post_id, name, keywords, message_template. Optional: keyword_match_mode (exact|contains|any, default exact), button_url, button_text, reply_to_comment, reply_template, delay_seconds (0-3600), is_active.

GET /automations/{id} - Get one automation

PUT /automations/{id} - Update automation

Same field validation as create, but all fields are optional. Returns the updated automation.

POST /automations/{id}/toggle - Activate/deactivate

Flips is_active. Returns the updated automation.

DELETE /automations/{id} - Delete automation

Returns { "deleted": true }.

GET /dm-logs - List DM delivery logs

Paginated. Filters: status (sent|failed|queued), automation_id, since (ISO 8601 timestamp).

curl "https://igmsg.com/api/v1/dm-logs?status=sent&since=2026-05-01T00:00:00Z" \
  -H "Authorization: Bearer igmsg_YOUR_TOKEN"

Webhooks

IGMsg sends HTTPS POST requests to URLs you register at igmsg.com/developer. Every request is signed and idempotency-safe.

Configuration

Each endpoint gets a unique signing secret (starts with whsec_) generated when you create it. Use this to verify webhook authenticity.

Available events

  • dm.sent - a DM was successfully delivered
  • dm.failed - a DM send failed (rate limit, expired token, etc.)
  • automation.created - new automation
  • automation.updated - automation modified
  • automation.toggled - activated or deactivated
  • automation.deleted - automation removed
  • instagram.connected - IG account linked
  • instagram.disconnected - IG account unlinked

Payload format

{
  "event": "dm.sent",
  "created_at": "2026-05-17T15:42:11+00:00",
  "data": {
    "dm_log_id": 9182,
    "automation_id": 47,
    "instagram_account_id": 7,
    "recipient_username": "fan_handle",
    "recipient_ig_id": "17841...",
    "comment_id": "17926...",
    "comment_text": "SHOP"
  }
}

Request headers

  • Content-Type: application/json
  • User-Agent: IGMsg-Webhook/1.0
  • X-IGMsg-Event - the event name (same as event in body)
  • X-IGMsg-Delivery - unique delivery ID (use to deduplicate retries)
  • X-IGMsg-Signature - t={timestamp},v1={hmac_sha256_signature}

Signature verification

The signature is computed as:

signed_payload = "{timestamp}.{request_body}"
signature = HMAC-SHA256(signed_payload, your_signing_secret)

Reject requests where the computed signature doesn't match. Also reject requests where timestamp is more than 5 minutes old (replay protection).

PHP example

$secret = 'whsec_...';
$header = $_SERVER['HTTP_X_IGMSG_SIGNATURE'] ?? '';
preg_match('/t=(\d+),v1=([a-f0-9]+)/', $header, $m);
[$_, $timestamp, $providedSig] = $m;

$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', "{$timestamp}.{$body}", $secret);

if (!hash_equals($expected, $providedSig)) {
    http_response_code(401);
    exit('Bad signature');
}

if (abs(time() - (int) $timestamp) > 300) {
    http_response_code(401);
    exit('Timestamp too old');
}

$payload = json_decode($body, true);
// ... handle event ...

Node.js example

const crypto = require('crypto');

function verify(req, secret) {
  const header = req.headers['x-igmsg-signature'] || '';
  const m = header.match(/t=(\d+),v1=([a-f0-9]+)/);
  if (!m) return false;
  const [, timestamp, providedSig] = m;
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) return false;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${req.rawBody}`)
    .digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(providedSig));
}

Retries

If your endpoint returns a non-2xx status (or times out after 30s), we retry with exponential backoff: 1 min, 5 min, 15 min, 1 hour, 4 hours. After 5 failed attempts the specific delivery is marked failed but the endpoint stays active. After 20 consecutive failures across deliveries, the endpoint is auto-disabled and you'll need to re-enable it from /developer.

Idempotency

The same event will never be delivered twice with the same X-IGMsg-Delivery header value. Store that ID server-side and ignore duplicates if you see one.

Need help?

Email hello@igmsg.com with the request ID (in any non-2xx API response under error.request_id) and we'll look it up.