webhooks

A change happened (notification to your app)

PlanVortex POSTs to the webhook_url of your client app when something happens. The body is an array of changes, and each one carries field telling you what it is — switch on it and ignore what you do not handle, because the list grows with the product.

Three shapes travel in that array. A change on an account (WebhookChange) always carries id_account, id_organization and social_network. A change on an integration (IntegrationWebhookChange) and the end of an AI plan (AiPlanWebhookChange) carry none of those, because they hang off the organization and not off any account.

The events delivered today are new_account, change_state_account, messages, messaging_postbacks, messaging_seen, messaging_error, comments, integration_error, ai_plan_generated and ai_plan_failed.

Comments

field: "comments" is the one for this section, and the comment itself travels in commentObj — a property of its own, never in messageObj. That is deliberate: a comment is not a message, it has no contact and it hangs off a publication. A consumer that only understands messages sees commentObj as absent and ignores the change, which is exactly what should happen.

Which networks arrive this way: Facebook and Instagram, because Meta pushes them. YouTube, LinkedIn and Google Business have no comment webhook, so those are polled by a background job and appear in the inbox within a few hours rather than instantly. X is polled too, and only when the organization turns it on, because reading replies there is billed per unit.

Meta repeats deliveries. The same comment can arrive more than once; the backend is idempotent about it and so should you be — deduplicate on commentObj.external_id.

AI plans

ai_plan_generated and ai_plan_failed say that a plan has finished: it was generated, or it gave up. Instead of polling the plan every few seconds, wait for one of these and then read it once with GET /clients/{id_client}/organizations/{id_organization}/ai_plans/{id_ai_plan}. The change carries ids and numbers only, never the prompt or the generated text, so the publications are in that GET.

A plan that hits a transient error goes back to the queue and is retried. That is not an end and sends nothing, so each plan sends one of these two events, never a failed followed by a generated. A plan generated with notices (warnings above zero) is still ai_plan_generated: it is usable, and the notices are in the plan.

The event goes to every app of the client that has a webhook_url, not only to the one that created the plan. And it is not retried: if your endpoint was down when the plan finished, the GET still tells you how it ended, so on startup read the plans you were waiting for.

Verifying the signature

Every delivery carries the body signed with your app's secret, twice:

  • x-hub-signature: sha1=<hmac>
  • x-hub-signature-256: sha256=<hmac>

Compute the HMAC over the raw request body — not over a re-serialized copy of the parsed JSON — and compare in constant time. Prefer the sha256 header.

Delivery is best effort: PlanVortex does not retry a webhook that fails.

postWebhookcomments

Payload

Example payload

[
  {
    "commentObj": {
      "id_publication": {
        "files": [
          {
            "file_properties": {
              "aspect_ratio": {
                "value": 1.7777777777777777,
                "text": "16:9"
              }
            },
            "public_path": "https://a1b2c3d4e5f6.eu.r2.cloudflarestorage.com/planvortex-prod/orgs/60b.../60c.../foto.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Signature=..."
          }
        ]
      }
    },
    "messageObj": {
      "message_options": {
        "files": [
          {
            "file_properties": {
              "aspect_ratio": {
                "value": 1.7777777777777777,
                "text": "16:9"
              }
            },
            "public_path": "https://a1b2c3d4e5f6.eu.r2.cloudflarestorage.com/planvortex-prod/orgs/60b.../60c.../foto.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=86400&X-Amz-Signature=..."
          }
        ]
      }
    }
  }
]

Response

Your endpoint acknowledged the delivery. PlanVortex does not retry: a delivery that fails is logged and dropped, so treat the inbox endpoints as the way to catch up on anything you missed.

Changes

Webhooks have no change history: a webhook is keyed by its event name rather than a path and method, so no recorded change can name it.