---
title: "ADR-007: Approval Step and Publishing Through a Webhook"
manual: "nr_repurpose"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-repurpose:adr-007@main"
source: "Adr/Adr007ApprovalAndWebhookPublishing.rst"
rendered: "2026-09-30T16:39:51+00:00"
---

# ADR-007: Approval Step and Publishing Through a Webhook {#adr-007}

-   *Status:* Accepted
-   *Date:* 2026-09-26
-   *Authors:* Netresearch DTT GmbH

## Context {#adr-007-context}

Every artifact is AI-generated. Before a post goes out under the company's
name, a person should have looked at it and said yes. The social posts are the
one format this extension could put on a network itself, at a planned time.

Posting to LinkedIn, X or Instagram directly needs an app registration and an
OAuth grant per network and per account, and every network changes its API on
its own schedule. Which networks and whose accounts is not a decision the
extension can make.

## Decision {#adr-007-decision}

**An approval state on every artifact.** `review_status` (open, approved,
rejected) with `reviewed_by` and `reviewed_at` on the artifact row. Users
whose groups grant the custom permission `nrrepurpose:approve_artifacts` (and
administrators) approve or reject a finished artifact in the result view. The
state has a reader: only an approved social post can be scheduled, and the
publishing command sends only approved posts.

**Scheduling on the artifact, publishing by a schedulable command.**
`publish_at` and `publish_status` (scheduled, publishing, published, failed)
on the row. `nr_repurpose:publish-due` sends every approved post whose time
has come. It claims a post with one conditional update (scheduled → publishing)
before sending, so two runs at the same time cannot send it twice. A refused
post becomes failed with the channel's reason and stays failed until an editor
schedules it again.

**The channel is a webhook.** `SocialPublisherInterface` has one
implementation, `WebhookSocialPublisher`: an HTTP POST of the post as JSON
(platform, text, publishing time, source URL without user name and password,
`aiGenerated` and the artifact's `aiLabel`) to the URL in `socialWebhookUrl`, optionally signed
with HMAC-SHA256 in `X-Nr-Repurpose-Signature`. A scheduling tool, an
automation service or an own endpoint takes it from there. Without a URL the
command sends nothing and says how many posts are due.

**Regenerating a job starts over.** The generator deletes a job's artifacts
before it runs again, and with them their review and schedule.

## Consequences {#adr-007-consequences}

-   **●  No network credentials in the extension; the network is chosen where the**

    webhook ends.

-   **●  A post carries its AI label into the channel (`aiGenerated`,**

    `aiLabel`), so the receiving side can disclose it.

◐  Publishing is as punctual as the scheduler runs the command.

-   **◐  The review of an image, a PDF or a podcast is recorded but has no reader**

    yet; it documents the decision for the editor's team.

-   **✕  Direct posting to a network needs its own `SocialPublisherInterface`**

    implementation with that network's credentials.
