OXIRANKER KEHITTÄJÄDOKUMENTAATIO

OxiRanker-dokumentaatio

Selkeä dokumentaatiokeskus verkkosivustosi yhdistämiseen OxiRanker-palveluihin. Aloita WordPress-integraatiosta tai API-integraatiosta ja seuraa sitten opasta vaihe vaiheelta.

Virallinen dokumentaatioTäysimittainen V1Sivustojen omistajille, kehittäjille ja integraatiotiimeille
Dokumentaatiovalikko
Valitse opas valikosta. Tämä sivu on OxiRankerin päädokumentaatiokeskus.
API-palvelu

API-palvelu

Käytä Export API:a ja Webhookia mukautetuille verkkosivustoille, mukautetuille CMS-alustoille sekä Next.js-, Laravel-, Node.js-, PHP- tai Python-backendeille.

Domain
Vahvistettu
Sisältö
Valmis
API
Webhook
Tärkeä tallennushuomautus

OxiRanker ei ole pysyvä hosting kohdesivustolle

OxiRanker ei anna pysyvän hostingin takuuta kohdesivuston sisällölle, kuville tai viedyille tiedostoille. OxiRanker ei ole käyttäjäsisällön hosting-palvelu, ja luotu sisältö, kansikuvat, Open Graph -kuvat tai tulostetiedostot eivät välttämättä säily OxiRankerin infrastruktuurissa yli 90 päivää.

Siksi tulosteen vastaanottamisen jälkeen kohdesivuston on tallennettava lopullinen artikkeli, kuvat, SEO-data, Open Graph -data ja Schema-data omaan tietokantaansa, palvelimeensa, tallennustilaansa tai CDN:äänsä, eikä sen pidä luottaa OxiRanker-URL-osoitteisiin julkisessa näytössä.

Koulutusvideo

Yhteyden koulutusvideo

Käytä tätä videota visuaalisena läpikäyntinä ennen WordPressin yhdistämistä tai API-integraation toteuttamista.

API-tuloste

Toteuta tulostevirta vaihe vaiheelta

Tallenna ensin Export Token ja Webhook Secret backendissäsi ja viimeistele sitten Webhook receiver, tietokanta, UPSERT-logiikka, Image Mirroring ja päivittäinen sync.

VAIHE 01

Rekisteröidy ja luo domain OxiRankerissa

Rekisteröidy ensin OxiRankeriin, kirjaudu tilillesi ja klikkaa Domains-osiosta Add Domain.

Oikea domain-muoto

Syötä Domain name -kenttään vain juuridomain. Osoitteessa ei saa olla http, https tai www.

example.com

Väärät muodot

Älä syötä näitä muotoja domain-kenttään.

https://example.com
http://example.com
www.example.com
Domainin luomisen jälkeen
Jos domainin muoto on kelvollinen, OxiRanker luo domainin ja vie sinut domainin omistuksen vahvistusvaiheeseen.
VAIHE 02

Vahvista domainin omistus

Tässä vaiheessa sinun on osoitettava, että domain kuuluu sinulle tai että sinulla on pääsy sen hallintaan.

DNS TXT

Suositeltu

Lisää TXT record domainin DNS:ään. Cloudflarella tämä on yleensä yksinkertaisin menetelmä.

DNS Name:
_yourdomain-verification.example.com

TXT Value:
acb1****29803

HTTP File

Aseta tekstitiedosto verkkosivustosi .well-known-polkuun, jotta OxiRanker voi vahvistaa sen.

File URL:
https://example.com/.well-known/yourdomain-verification.txt

File Content:
220daaa37****42730f27

Meta Tag

Aseta meta tag verkkosivustosi etusivun head-osioon.

<meta name="yourdomain-verification" content="190208d****d275b65" />
Tärkeä DNS-huomautus
DNS recordin lisäämisen jälkeen, erityisesti Cloudflaressa, sinun on ehkä odotettava 2–3 minuuttia. Klikkaa sitten Confirm samalla OxiRanker-sivulla.
Vahvistustokenin voimassaoloaika
Domainin vahvistusarvoilla on aikaraja. Tällä hetkellä vahvistusarvot ovat yleensä voimassa 1440 minuuttia.
VAIHE 03

Vastaanota ilmaisia tokeneita ensimmäisen domainin vahvistamisen jälkeen

Ensimmäisen domainin vahvistamisen jälkeen OxiRanker lisää tilillesi 1000 ilmaista tokenia alkutestausta varten.

Vain ensimmäiselle domainille
Tämä lahja lisätään vain ensimmäiselle vahvistetulle domainille eikä sitä lisätä uudelleen myöhemmille domaineille.
VAIHE 04

Määritä kielet ja Blog URL

Blog URLs -osiossa määritä, onko verkkosivustosi yksikielinen vai monikielinen ja mitä kieliä luoduissa artikkeleissa käytetään.

Valitse kielet

OxiRanker tukee 24 kieltä. Voit valita yhden tai useamman kielen verkkosivustosi rakenteen mukaan.

Syötä Blog URL

Syötä jokaiselle kielelle kyseisen kielen blogin URL. Jos verkkosivustosi on monikielinen, syötä jokaisen kielen polku erikseen.

https://example.com/fa/blog
https://example.com/en/blog
URL-rakenne
Jos verkkosivustollasi on erilainen rakenne, syötä verkkosivustosi todellinen blogin URL. Tärkeää on, että lopullinen URL on polku, johon artikkelit julkaistaan.
VAIHE 05

Lisää avainsanat jokaiselle kielelle

Lisää Keywords-välilehdellä jokaiselle kielelle vähintään 3 ja enintään 50 avainsanaa.

Pääsääntö

Jokaisen kielen avainsanat on syötettävä samalla kielellä. Jos artikkelin kieli on englanti, myös avainsanojen on oltava englanniksi.

Kuinka avainsanoja lisätään

Kirjoitettuasi jokaisen avainsanan paina Enter lisätäksesi sen. Kun kaikki avainsanat ovat valmiit, klikkaa Save.

Yleinen virhe
Jos syötät turkinkielisiä avainsanoja englannin kielelle, englanninkieliseen sisältöön voi ilmestyä turkinkielisiä lauseita. Avainsanojen kielen on vastattava artikkelin kieltä.
VAIHE 06

Täydennä domainin Article Profile

Article Profile kertoo OxiRankerille, mikä brändisi on, kenelle se kirjoittaa, mitä sävyä sen tulee käyttää ja mitä aiheita sen tulee välttää.

Toimiala

Määrittää markkinan tai liiketoimintakategorian. Esimerkki: kiinteistökonsultointi sekä kiinteistöjen osto ja myynti Dubaissa.

Liiketoiminnan kuvaus

Selittää, mitä yritys myy, ketä se palvelee, miten se toimii ja mikä tekee siitä erilaisen.

Yleisö

Määrittää, kenelle sisällön tulee puhua. Jokainen yleisö voidaan tallentaa erikseen.

Sävy

Ohjaa luodun sisällön tyyliä ja ääntä, kuten muodollinen, opettavainen, yksinkertainen, tekninen tai myyntiorientoitunut.

Sisällön tavoite

Määrittää, mitä artikkeleiden tulee saavuttaa, kuten asiakkaiden houkutteleminen, liikenteen kasvattaminen tai käyttäjien kouluttaminen.

Kielletyt aiheet

Kaikki, mistä brändin ei pitäisi puhua tai mihin sitä ei pitäisi yhdistää, syötetään tähän.

Valinnainen CTA

CTA tarkoittaa toimintoa, jonka haluat lukijan tekevän artikkelin lukemisen jälkeen. Jos et ole varma sopivasta tekstistä, tämä kenttä on parempi jättää tyhjäksi.

Monikielinen profiili
Jos verkkosivustosi on monikielinen, sinun tarvitsee täydentää profiili vain yhdellä kielellä. OxiRanker voi valmistella tarvittavat versiot muille kielille saman profiilin perusteella.
VAIHE 07

Sisäinen ja ulkoinen linkitys

OxiRanker voi älykkäästi lisätä sisäisiä ja ulkoisia linkkejä artikkeleihin.

Sisäiset linkit

Tämä vaihtoehto on oletuksena käytössä, ja se kannattaa pitää käytössä. Sisäinen linkitys auttaa hakukoneita ymmärtämään verkkosivustosi sisältörakenteen paremmin.

Ensimmäisessä artikkelissa sisäisiä linkkejä ei yleensä luoda, koska aiempaa artikkelia ei vielä ole. Toisesta artikkelista eteenpäin järjestelmä voi tehdä sisäistä linkitystä.

Ulkoiset linkit

Ulkoisia linkkejä lisätään virallisiin ja luotettaviin lähteisiin, ja ne merkitään nofollow-muotoon, jotta SEO-auktoriteetti ei siirry tahattomasti.

VAIHE 08

Tarkista profiilin laatu AI:n avulla

Article Profilen täydentämisen jälkeen OxiRanker tarkistaa profiilin laadun.

85+
Tallentamiseen vaadittu vähimmäispistemäärä
95
Erinomainen pistemäärä, joka saavutetaan usein parannusten jälkeen
AI
Ammattilaiset ehdotukset jokaiselle kentälle
Jos pistemäärä on alle 85
Järjestelmä ei salli lopullista tallennusta ja näyttää selityksiä sekä parannusehdotuksia jokaiselle ongelmalliselle kentälle. Voit ottaa ehdotukset käyttöön ja tallentaa uudelleen.
VAIHE 09

Yhdistä Telegram artikkeliyhteenvetoja varten

Kun profiili on tallennettu onnistuneesti, voit ottaa käyttöön artikkeliyhteenvetojen lähettämisen Telegram-kanavalle.

1

Lisää botti

Lisää @Oxiranker_Bot Telegram-kanavallesi ja tee siitä Admin.

2

Syötä kanavan käyttäjänimi

Syötä kanavan nimi @-merkin kanssa. Esimerkki: @my_channel

3

Ota yhteenvedon toimitus käyttöön

Kun kelvollinen kanava on rekisteröity, ota artikkeliyhteenvedon toimitus käyttöön.

Telegramin hyöty
Jos käytössä, sisällön luomisen jälkeen artikkelin yhteenveto lähetetään Telegram-kanavalle kuvan ja linkin kanssa. Tämä voi vahvistaa liikennereittejä verkkosivustollesi.
VAIHE 10

Määritä automaattinen artikkelimoottori

Automatic article generation -välilehdellä voit hallita automaattista artikkelien luontimoottoria.

Älykäs aiheen valinta

Järjestelmä käyttää avainsanoja ja Article Profilea valitakseen sopivia, uniikkeja ja julkaisukelpoisia aiheita.

Bränditietoinen luonti

Sävy, yleisö, sisällön tavoite ja brändirajoitukset otetaan huomioon artikkelin luonnin aikana.

Ajastettu julkaisu

Kuukausikapasiteetti jaetaan kuukauden ajalle, jotta sisällön julkaisu näyttää luonnollisemmalta ja johdonmukaisemmalta.

Kuukausikapasiteetti

7 articles / month
10 articles / month
15 articles / month
30 articles / month

30 artikkelia tarkoittaa lähes yhtä artikkelia päivässä. 15 artikkelia tarkoittaa lähes joka toinen päivä. 10 artikkelia tarkoittaa noin kahden tai kolmen päivän välein. 7 artikkelia tarkoittaa noin kolmen tai neljän päivän välein.

Artikkelin pituus ja kuva

Artikkelin pituus voi olla Short, Standard tai Long. Standard on tasapainoinen valinta useimmille verkkosivustoille.

Artikkelikuvat voidaan ottaa käyttöön tai poistaa käytöstä, ja kuvan teema voi olla Light tai Dark.

Huomautus API-sivustoille
Automaattinen moottori luo artikkelit OxiRankerin sisällä. Jos haluat siirtää jokaisen artikkelin mukautetulle verkkosivustollesi luonnin jälkeen, sinun on toteutettava Export API, Webhook, tietokantatallennus ja Image Mirroring.
VAIHE 11

Luo ensimmäinen artikkeli Generate-osiosta

Kun domain on valmisteltu, avaa Generate-valikko, valitse domain ja klikkaa New Content tai New Article.

1

Kieli

Jokaiselle sisältöpyynnölle valitaan vain yksi kieli.

2

Aihe

Kirjoita selkeä aihe. Source URL on valinnainen.

3

Kuva

Ota kuva käyttöön tai poista se käytöstä ja valitse Light- tai Dark-teema.

4

Maksu ja luonti

Kustannus vähennetään walletista ja artikkelin luonti alkaa.

Luontiaika
Artikkelin ja kuvan luonti kestää yleensä noin 2–3 minuuttia. Valmistumisen jälkeen artikkeli näkyy OxiRankerissa.
Source URL
Jos Source URL syötetään, OxiRanker käyttää sitä aiheen parempaan ymmärtämiseen, mutta se ei luo kopioitua sisältöä. Järjestelmä on suunniteltu luomaan uniikkia sisältöä.
VAIHE 12

Tarkista Preview, SEO Report ja JSON Output

Kun artikkeli on luotu, useita tärkeitä tulosteita on saatavilla laadun tarkistukseen ja julkaisun valmisteluun.

Preview

Näyttää artikkelin tekstin ja luodun kuvan. Tämä osio on hyödyllinen lopullisen laadun tarkistamiseen ennen julkaisua.

SEO Report

Näyttää ymmärrettävän SEO-raportin, joka sisältää SEO Score, title, meta description, canonical, keywords, schema, headings, links, images ja OpenGraph.

JSON Output

Näyttää teknisille käyttäjille artikkelin täydellisen tulosteen, mukaan lukien slug, html_body, schema, kuva-URL:t, tags, category, lukuaika sekä luonti- ja päivitysaikaleimat.

Tulosteiden merkitys
Nämä tulosteet osoittavat, että artikkeli ei ole pelkkää tekstiä. Se on rakenteista, SEO-valmista sisältöä, joka on valmisteltu WordPressiä, CMS:ää, API:a ja julkaisujärjestelmiä varten.
VAIHE 13

Mitä Export API ja Webhook tarkalleen tekevät?

Kun artikkeli on luotu OxiRankerissa, kohdesivusto voi lukea sen Export API:lla tai saada ilmoituksen Webhookin kautta, kun artikkeli on valmis.

Menetelmä 1: Export API

Kohdesivusto voi lukea artikkeleita OxiRankerista Export Tokenin avulla aina tarvittaessa. Tätä menetelmää käytetään manuaaliseen synciin, päivittäiseen synciin, artikkelilistaussivuihin ja täydellisen artikkeli-JSONin hakemiseen.

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Menetelmä 2: Webhook

Kun uusi artikkeli luodaan tai artikkeli päivitetään, OxiRanker ilmoittaa kohdesivuston endpointille. Webhook ei lähetä koko artikkelia. Se lähettää vain artikkelin tunnisteen, jotta kohdesivusto voi hakea koko artikkelin Export JSON API:sta.

https://example.com/api/oxiranker/webhook
{
  "event": "article.created",
  "domain_id": "1",
  "article_id": "5",
  "request_id": "5",
  "lang": "fa",
  "slug": "smart-internal-links",
  "occurred_at": "2026-05-20T19:35:33.000Z"
}
Erittäin tärkeä huomautus
Webhook on vain kevyt viesti. Kohdesivuston ei pidä odottaa koko artikkelia Webhookin sisällä. Kun domain_id ja article_id on vastaanotettu, sen on haettava täydellinen artikkeli Export JSON API:sta ja tallennettava se omaan tietokantaansa.
VAIHE 14

Sisällön tallennusvastuu ja Image Mirroring

Kohdesivuston on tallennettava lopullinen artikkeli ja kuvat omalle infrastruktuurilleen.

Mitä kohdesivuston on tallennettava

Koko artikkelin teksti kohdesivuston tietokannassa
Artikkelin pääkuva kohdepalvelimella, tallennustilassa tai CDN:ssä
Open Graph -kuva kohdepalvelimella, tallennustilassa tai CDN:ssä
SEO-, OpenGraph- ja Article Schema -data
raw_detail_payload ja raw_list_item alkuperäisen tulosteen säilyttämiseksi

Oikea kuvan polun esimerkki

OxiRanker-kuva-URLien suora ja pysyvä käyttö ei ole oikein. Kuva on ladattava ja artikkelin URLit on korvattava kohdesivuston sisäisillä URLeilla.

https://oxiranker.com/uploads/articles/3_5/cover.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
OxiRanker ei ole pysyvä hosting kohdesivustolle
OxiRanker ei ole vastuussa kohdesivuston tulostesisällön ja kuvien pysyvästä tallennuksesta. OxiRanker ei ole käyttäjäsisällön hosting-palvelu, ja luotu sisältö, kansikuvat, Open Graph -kuvat tai tulostetiedostot eivät välttämättä säily OxiRankerin infrastruktuurissa yli 90 päivää. Siksi kohdesivuston on tallennettava lopullinen artikkeli, kuvat, SEO-data, OpenGraph ja Schema omaan tietokantaansa, palvelimeensa, tallennustilaansa tai CDN:äänsä.
VAIHE 15

Hanki Export Token ja valmiit endpointit

Export Token ja käyttövalmiit endpointit ovat saatavilla artikkelisivulta, Outputs-välilehdeltä ja API Output -osiosta.

Mistä Export Token saadaan

Luo ensimmäinen artikkeli OxiRankerissa.
Avaa kyseinen artikkelisivu.
Avaa Outputs-välilehti.
Avaa API Output -osio.
Luo token Token managementista.
Koko token näytetään vain kerran.
Tallenna token kohdesivuston backendiin tai .env-tiedostoon.
https://oxiranker.com/en/pannel/generate/{domainId}/articles/{articleId}

Käyttövalmiit endpointit

Samalla artikkelisivulla API Output -osion sisällä Ready-to-use endpoints näyttää valmiit URLit.

https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=20&offset=0
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/json
https://oxiranker.com/api/v1/users/domains/1/articles/1/export/html?canonical_mode=absolute&include_css=0
Tokenin kierrättäminen
Jos Export Token kierrätetään OxiRanker-paneelissa, aiempi token vanhenee. Uuden arvon on välittömästi korvattava vanha arvo kohdesivuston .env-tiedostossa tai backend-ympäristössä, ja kohdebackend on käynnistettävä uudelleen tai redeployattava.
VAIHE 16

Luo Webhook Secret ja kohde-endpoint

Webhook Secret luodaan myös artikkelisivulta, Outputs-välilehdeltä ja API Output -osiosta.

Missä Webhook Secret luodaan?

Avaa artikkelisivu.
Avaa Outputs-välilehti.
Avaa API Output -osio.
Luo secret Webhook Secret -osiossa.
Koko secret näytetään vain kerran.
Tallenna secret kohdesivuston .env-tiedostoon.

Kiinteä kohde-endpoint

OxiRanker hyväksyy kohde-Webhookille vain seuraavan kiinteän polun. Kohdesivuston on luotava tämä endpoint omaan backendiinsä.

POST /api/oxiranker/webhook

https://example.com/api/oxiranker/webhook

Tämän endpointin on hyväksyttävä vain POST, säilytettävä raw body, vahvistettava timestamp ja signature sekä palautettava onnistunut vastaus webhook.testille.

Aktiiviset tapahtumat
Nykyisessä versiossa tapahtumat ovat kiinteät: article.created ja article.updated. Yhteyden testaamiseen lähetetään event-arvo webhook.test.
VAIHE 17

Kohdesivuston vaaditut .env-arvot

OxiRankerilla ei ole pääsyä kohdesivuston tiedostoihin tai .env-tiedostoon. Kohdesivuston kehittäjän on tallennettava nämä arvot omaan backendiinsä.

OXIRANKER_EXPORT_API_BASE_URL=https://oxiranker.com
OXIRANKER_EXPORT_API_TOKEN=YOUR_EXPORT_TOKEN
OXIRANKER_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET
OXIRANKER_WEBHOOK_MAX_SKEW_MS=300000
OXIRANKER_SOURCE_PROVIDER=oxiranker
OXIRANKER_SOURCE_DOMAIN_IDS=1

SITE_ARTICLE_IMAGE_UPLOAD_DIR=/var/www/example.com/uploads/site-articles
SITE_ARTICLE_IMAGE_MAX_BYTES=10485760

PUBLIC_BASE_URL=https://example.com

OXIRANKER_SYNC_ENABLED=true
OXIRANKER_SYNC_HOUR=3
OXIRANKER_SYNC_MINUTE=10
OXIRANKER_SYNC_PAGE_LIMIT=20
OXIRANKER_SYNC_MAX_PAGES=50

Turva-arvot

OXIRANKER_EXPORT_API_TOKEN
Export API -token on tallennettava vain backendiin.
OXIRANKER_WEBHOOK_SECRET
Webhook Secret on tallennettava vain backendiin.
OXIRANKER_WEBHOOK_MAX_SKEW_MS
Webhookin suurin sallittu aikaero. 300000 tarkoittaa 5 minuuttia.

Tallennusarvot

SITE_ARTICLE_IMAGE_UPLOAD_DIR
Fyysinen polku mirrorattujen kuvien tallentamiseen kohdepalvelimelle.
SITE_ARTICLE_IMAGE_MAX_BYTES
Suurin sallittu kuvan koko latausta ja tallennusta varten.
PUBLIC_BASE_URL
Kohdesivuston julkinen URL, jota käytetään lopullisten kuva- ja artikkeli-URLien luomiseen.
Frontend-turvallisuus
Export Tokenia ja Webhook Secretia ei saa sijoittaa frontend-koodiin, käyttäjälle näkyvään JavaScriptiin, localStorageen tai window-objektiin.
VAIHE 18

Export API: List, JSON, HTML ja ZIP

Kohdesivusto voi käyttää Export API:a artikkelilistan tai jokaisen artikkelin täydellisen tulosteen vastaanottamiseen.

Hae domainin artikkelilista

GET https://oxiranker.com/api/v1/users/domains/:domainId/export/articles?limit=20&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

limit määrittää artikkelien määrän ja on tällä hetkellä rajattu välille 1–50. offsetia käytetään sivutukseen. lang on valinnainen ja sitä käytetään tietyn kielen, kuten fa, en tai tr, artikkelien hakemiseen.

Hae koko artikkeli JSON-muodossa

GET https://oxiranker.com/api/v1/users/domains/:domainId/articles/:articleId/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Tämä on kohdesivuston tärkein tuloste. Kohdesivuston on tallennettava koko artikkeli-JSON omaan tietokantaansa ja luettava julkista näyttöä varten omasta tietokannastaan.

Hae valmis HTML

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/html?canonical_mode=absolute&include_css=0
Authorization: Bearer YOUR_EXPORT_TOKEN

HTML sopii sivustoille, jotka haluavat valmiin tulosteen, mutta ammattimaisempi tapa on tallentaa JSON ja renderöidä se kohdesivuston omalla mallipohjalla.

Hae ZIP-tuloste

GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/zip
Authorization: Bearer YOUR_EXPORT_TOKEN

ZIP-tuloste sisältää tiedostot article.json, meta.json, assets.json, schema.jsonld, index.html, version.txt ja README.txt.

VAIHE 19

Webhook-allekirjoitus ja turvallinen Webhookin vastaanotto

Kohdesivuston on vahvistettava Webhook-allekirjoitus alkuperäisen raw bodyn avulla.

Webhook-headerit

content-type: application/json
user-agent: Oxiranker-Webhooks/1.0
x-oxiranker-event: article.created
x-oxiranker-domain-id: 1
x-oxiranker-timestamp: 2026-05-20T19:35:33.000Z
x-oxiranker-signature: sha256=...

Allekirjoituskaava

payload_to_sign = timestamp + "." + rawBody
signature = "sha256=" + HMAC_SHA256(webhook_secret, payload_to_sign)

Jos kohdesivusto parsii JSONin ensin ja stringifyaa sen sitten uudelleen, järjestys tai välilyönnit voivat muuttua ja allekirjoitus voi muuttua virheelliseksi. Alkuperäistä raw bodya on käytettävä allekirjoituksen vahvistamiseen.

Timing-safe-vertailu
Älä vertaa allekirjoituksia muodolla expected === received. Node.js:ssä on parempi käyttää crypto.timingSafeEqualia.
VAIHE 20

Yksinkertainen Node.js-koodiesimerkki Webhookin vastaanottamiseen

Tämä Express-esimerkki käsittelee raw bodyn, timestampin, signaturen, webhook.testin ja artikkelitapahtumat.

import express from "express";
import crypto from "crypto";

const app = express();

app.use(
  express.json({
    verify: (req, _res, buf) => {
      (req as any).rawBody = Buffer.from(buf);
    },
  })
);

function getHeader(req: express.Request, name: string): string {
  const value = req.headers[name.toLowerCase()];
  if (Array.isArray(value)) return String(value[0] || "").trim();
  return String(value || "").trim();
}

function getRawBody(req: express.Request): string {
  const raw = (req as any).rawBody;
  if (Buffer.isBuffer(raw)) return raw.toString("utf8");
  return JSON.stringify(req.body || {});
}

function createSignature(secret: string, timestamp: string, rawBody: string): string {
  const payload = `${timestamp}.${rawBody}`;
  const digest = crypto
    .createHmac("sha256", secret)
    .update(payload, "utf8")
    .digest("hex");

  return `sha256=${digest}`;
}

function timingSafeEqualText(a: string, b: string): boolean {
  const ab = Buffer.from(a, "utf8");
  const bb = Buffer.from(b, "utf8");

  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

function isTimestampValid(timestamp: string): boolean {
  const maxSkewMs = Number(process.env.OXIRANKER_WEBHOOK_MAX_SKEW_MS || 300000);
  const date = new Date(timestamp);

  if (Number.isNaN(date.getTime())) return false;

  const diff = Math.abs(Date.now() - date.getTime());
  return diff <= maxSkewMs;
}

app.post("/api/oxiranker/webhook", async (req, res) => {
  const secret = String(process.env.OXIRANKER_WEBHOOK_SECRET || "").trim();

  if (!secret) {
    return res.status(500).json({
      error: "webhook_secret_missing",
      message: "Webhook secret is not configured.",
    });
  }

  const eventHeader = getHeader(req, "x-oxiranker-event");
  const domainIdHeader = getHeader(req, "x-oxiranker-domain-id");
  const timestamp = getHeader(req, "x-oxiranker-timestamp");
  const receivedSignature = getHeader(req, "x-oxiranker-signature");
  const rawBody = getRawBody(req);

  if (!eventHeader) {
    return res.status(400).json({
      error: "missing_webhook_event",
      message: "Webhook event header is required.",
    });
  }

  if (!domainIdHeader) {
    return res.status(400).json({
      error: "missing_webhook_domain_id",
      message: "Webhook domain id header is required.",
    });
  }

  if (!timestamp) {
    return res.status(400).json({
      error: "missing_webhook_timestamp",
      message: "Webhook timestamp header is required.",
    });
  }

  if (!receivedSignature) {
    return res.status(400).json({
      error: "missing_webhook_signature",
      message: "Webhook signature header is required.",
    });
  }

  if (!isTimestampValid(timestamp)) {
    return res.status(401).json({
      error: "invalid_webhook_timestamp",
      message: "Webhook timestamp is expired or invalid.",
    });
  }

  const expectedSignature = createSignature(secret, timestamp, rawBody);

  if (!timingSafeEqualText(expectedSignature, receivedSignature)) {
    return res.status(401).json({
      error: "invalid_webhook_signature",
      message: "Invalid webhook signature.",
    });
  }

  const payload = req.body || {};
  const event = String(payload.event || eventHeader || "").trim();

  if (event === "webhook.test") {
    const sourceDomainId = Number(payload.domain_id || domainIdHeader);

    return res.status(200).json({
      ok: true,
      message: "Webhook test received successfully.",
      event,
      source_domain_id:
        Number.isFinite(sourceDomainId) && sourceDomainId > 0 ? sourceDomainId : null,
      test: true,
    });
  }

  if (event !== "article.created" && event !== "article.updated") {
    return res.status(400).json({
      error: "unsupported_webhook_event",
      message: "Webhook event is not supported.",
    });
  }

  const sourceDomainId = Number(payload.domain_id || domainIdHeader);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    return res.status(400).json({
      error: "invalid_domain_id",
      message: "Webhook domain id is invalid.",
    });
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    return res.status(400).json({
      error: "invalid_article_id",
      message: "Webhook article id is invalid.",
    });
  }

  // Fetch the full article from Export API and store it in the database here.
  // await syncArticleFromOxiranker(sourceDomainId, sourceArticleId);

  return res.status(200).json({
    ok: true,
    message: "Webhook processed successfully.",
    event,
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
  });
});
VAIHE 21

Suositeltu kohdesivuston tietokanta

Kohdesivuston on tallennettava artikkelit omaan tietokantaansa ja estettävä duplikaatit unique constraintilla.

CREATE TABLE public.site_articles (
  id BIGSERIAL PRIMARY KEY,

  source_provider VARCHAR NOT NULL DEFAULT 'oxiranker',
  source_domain_id BIGINT NOT NULL,
  source_article_id BIGINT NOT NULL,
  source_request_id BIGINT NULL,

  lang VARCHAR NOT NULL,
  lang_label VARCHAR NULL,
  dir VARCHAR NOT NULL,
  is_rtl BOOLEAN NULL,

  length VARCHAR NULL,
  topic_text TEXT NULL,

  slug VARCHAR NOT NULL,
  canonical_path VARCHAR NULL,
  effective_canonical_url TEXT NULL,

  title TEXT NOT NULL,
  meta_description TEXT NULL,
  html_body TEXT NULL,

  focus_keywords JSONB NOT NULL DEFAULT '[]'::jsonb,
  category VARCHAR NULL,
  tags JSONB NOT NULL DEFAULT '[]'::jsonb,

  reading_time_minutes INTEGER NULL,

  og_title TEXT NULL,
  og_description TEXT NULL,

  cover_image_url TEXT NULL,
  og_image_url TEXT NULL,

  article_schema JSONB NOT NULL DEFAULT '{}'::jsonb,

  raw_list_item JSONB NOT NULL DEFAULT '{}'::jsonb,
  raw_detail_payload JSONB NOT NULL DEFAULT '{}'::jsonb,

  source_created_at TIMESTAMPTZ NULL,
  source_updated_at TIMESTAMPTZ NULL,

  sync_status VARCHAR NOT NULL DEFAULT 'synced',
  last_synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  last_sync_error TEXT NULL,

  publish_status VARCHAR NOT NULL DEFAULT 'draft',
  published_at TIMESTAMPTZ NULL,
  deleted_at TIMESTAMPTZ NULL,

  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),

  source_cover_image_url TEXT NULL,
  source_og_image_url TEXT NULL,

  image_mirror_status VARCHAR NOT NULL DEFAULT 'pending',
  image_mirror_error TEXT NULL,
  image_mirrored_at TIMESTAMPTZ NULL,

  CONSTRAINT site_articles_dir_check
    CHECK (dir IN ('ltr', 'rtl')),

  CONSTRAINT site_articles_reading_time_check
    CHECK (reading_time_minutes IS NULL OR reading_time_minutes >= 0),

  CONSTRAINT site_articles_sync_status_check
    CHECK (sync_status IN ('pending', 'syncing', 'synced', 'failed')),

  CONSTRAINT site_articles_publish_status_check
    CHECK (publish_status IN ('draft', 'published', 'archived')),

  CONSTRAINT site_articles_image_mirror_status_check
    CHECK (image_mirror_status IN ('pending', 'not_needed', 'mirrored', 'failed')),

  CONSTRAINT site_articles_source_unique
    UNIQUE (source_provider, source_domain_id, source_article_id)
);
CREATE INDEX site_articles_source_lookup_idx
ON public.site_articles (source_provider, source_domain_id, source_article_id);

CREATE INDEX site_articles_source_updated_at_idx
ON public.site_articles (source_updated_at DESC);

CREATE INDEX site_articles_slug_idx
ON public.site_articles (slug)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX site_articles_lang_slug_active_unique
ON public.site_articles (lang, slug)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_lang_publish_idx
ON public.site_articles (lang, publish_status)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_published_at_idx
ON public.site_articles (published_at DESC)
WHERE deleted_at IS NULL;

CREATE INDEX site_articles_focus_keywords_gin_idx
ON public.site_articles USING gin (focus_keywords);

CREATE INDEX site_articles_tags_gin_idx
ON public.site_articles USING gin (tags);

CREATE INDEX site_articles_article_schema_gin_idx
ON public.site_articles USING gin (article_schema);
PostgreSQL-huomautus
PostgreSQL:ssä persialaiselle tai Unicode-tekstille käytetään yksinkertaisia lainausmerkkejä. Jotain kuten N'...' ei tarvita eikä sitä pidä käyttää.
VAIHE 22

Estä duplikaatit UPSERTilla

Artikkelia ei saa lisätä uudelleen jokaisesta Webhookista. Jos se on jo olemassa, se on päivitettävä.

Pääavain duplikaattien estämiseen
Tärkein constraint duplikaattien estämiseen on: UNIQUE (source_provider, source_domain_id, source_article_id)
INSERT INTO public.site_articles (
  source_provider,
  source_domain_id,
  source_article_id,
  source_request_id,
  lang,
  dir,
  slug,
  title,
  meta_description,
  html_body,
  focus_keywords,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  article_schema,
  source_created_at,
  source_updated_at,
  sync_status,
  last_synced_at,
  publish_status,
  published_at,
  created_at,
  updated_at
)
VALUES (
  'oxiranker',
  1,
  5,
  5,
  'fa',
  'rtl',
  'smart-internal-links',
  'Article title',
  'Meta description',
  '<p>HTML body</p>',
  '[]'::jsonb,
  'SEO',
  '[]'::jsonb,
  7,
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  'https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp',
  '{}'::jsonb,
  '2026-05-20T19:33:23.618Z',
  '2026-05-20T19:34:09.629Z',
  'synced',
  now(),
  'published',
  now(),
  now(),
  now()
)
ON CONFLICT (source_provider, source_domain_id, source_article_id)
DO UPDATE SET
  source_request_id = EXCLUDED.source_request_id,
  lang = EXCLUDED.lang,
  dir = EXCLUDED.dir,
  slug = EXCLUDED.slug,
  title = EXCLUDED.title,
  meta_description = EXCLUDED.meta_description,
  html_body = EXCLUDED.html_body,
  focus_keywords = EXCLUDED.focus_keywords,
  category = EXCLUDED.category,
  tags = EXCLUDED.tags,
  reading_time_minutes = EXCLUDED.reading_time_minutes,
  cover_image_url = EXCLUDED.cover_image_url,
  og_image_url = EXCLUDED.og_image_url,
  article_schema = EXCLUDED.article_schema,
  source_created_at = EXCLUDED.source_created_at,
  source_updated_at = EXCLUDED.source_updated_at,
  sync_status = 'synced',
  last_synced_at = now(),
  last_sync_error = NULL,
  publish_status = CASE
    WHEN public.site_articles.publish_status = 'archived'
    THEN public.site_articles.publish_status
    ELSE 'published'
  END,
  published_at = COALESCE(public.site_articles.published_at, EXCLUDED.published_at, now()),
  deleted_at = NULL,
  updated_at = now();
VAIHE 23

Hae koko artikkeli ja syncaa Webhookin jälkeen

Kun Webhook saapuu, kohdesivuston on haettava koko artikkeli Export API:sta, mirrorattava kuvat ja tehtävä artikkelille UPSERT.

async function fetchOxirankerArticleDetail(args: {
  baseUrl: string;
  token: string;
  domainId: number;
  articleId: number;
}) {
  const url = `${args.baseUrl.replace(/\/+$/, "")}/api/v1/users/domains/${args.domainId}/articles/${args.articleId}/export/json`;

  const response = await fetch(url, {
    method: "GET",
    headers: {
      Authorization: `Bearer ${args.token}`,
      Accept: "application/json",
      "User-Agent": "My-Site-Oxiranker-Sync/1.0",
    },
  });

  const bodyText = await response.text();

  if (response.status < 200 || response.status > 299) {
    throw new Error(
      `Export API request failed with HTTP status ${response.status}. Response: ${bodyText.slice(0, 1000)}`
    );
  }

  try {
    return JSON.parse(bodyText);
  } catch {
    throw new Error("Export API returned invalid JSON.");
  }
}
async function syncArticleFromWebhook(payload: any) {
  const sourceDomainId = Number(payload.domain_id);
  const sourceArticleId = Number(payload.article_id);

  if (!Number.isFinite(sourceDomainId) || sourceDomainId <= 0) {
    throw new Error("Webhook domain id is invalid.");
  }

  if (!Number.isFinite(sourceArticleId) || sourceArticleId <= 0) {
    throw new Error("Webhook article id is invalid.");
  }

  const article = await fetchOxirankerArticleDetail({
    baseUrl: process.env.OXIRANKER_EXPORT_API_BASE_URL!,
    token: process.env.OXIRANKER_EXPORT_API_TOKEN!,
    domainId: sourceDomainId,
    articleId: sourceArticleId,
  });

  const mirroredImages = await mirrorArticleImages(article);

  const finalHtmlBody = replaceImageUrls(article.html_body, mirroredImages);
  const finalArticleSchema = replaceImageUrlsInJson(article.article_schema, mirroredImages);

  await upsertSiteArticle({
    source_provider: process.env.OXIRANKER_SOURCE_PROVIDER || "oxiranker",
    source_domain_id: sourceDomainId,
    source_article_id: sourceArticleId,
    source_request_id: article.request_id,
    lang: article.lang,
    dir: article.dir,
    slug: article.slug,
    title: article.title,
    meta_description: article.meta_description,
    html_body: finalHtmlBody,
    focus_keywords: article.focus_keywords,
    category: article.category,
    tags: article.tags,
    reading_time_minutes: article.reading_time_minutes,
    cover_image_url: mirroredImages.cover_image_url,
    og_image_url: mirroredImages.og_image_url,
    article_schema: finalArticleSchema,
    raw_detail_payload: article,
    source_created_at: article.created_at,
    source_updated_at: article.updated_at,
  });
}
VAIHE 24

Image Mirroring ja kuvien tallennussäännöt

Kohdesivuston on ladattava kuvat, tallennettava ne omaan infrastruktuuriinsa ja korvattava artikkelin URLit.

Image Mirroring -säännöt

Vain HTTPS-URLit on hyväksyttävä.
Tiedoston on oikeasti oltava kuva.
content-type-arvon on alettava image/.
Tyhjiä tiedostoja ei saa tallentaa.
Tiedoston koko ei saa ylittää rajaa.
Jos cover ja og ovat samat, lataa vain kerran.
Mirroringin jälkeen korvaa URLit html_bodyssa ja article_schemassa.

Suositeltu tallennuspolku

/uploads/site-articles/oxiranker/1/5/cover.webp
/uploads/site-articles/oxiranker/1/5/og.webp
https://example.com/uploads/site-articles/oxiranker/1/5/cover.webp
VAIHE 25

Päivittäinen sync luotettavuutta varten

Webhook yksin ei riitä. Kohdesivustolla pitäisi olla myös päivittäinen sync, jotta puuttuneet artikkelit voidaan palauttaa.

Miksi päivittäinen sync tarvitaan?

Kohdesivusto voi olla väliaikaisesti alhaalla.
Webhook voi aikakatkaista.
Deployment- tai verkko-ongelmia voi tapahtua.
Artikkeli voidaan luoda, mutta sen Webhook voi jäädä väliin.

Päivittäisen syncin algoritmi

Aloita offset arvosta 0.
Käytä limit-arvoa 20 tai 50.
Hae artikkelilista Export List API:sta.
Hae jokaiselle kohteelle kyseisen artikkelin Export JSON.
Mirroroi kuvat.
Tallenna artikkeli UPSERTilla.
Kasvata offsetia ja jatka, kunnes total on valmis.
VAIHE 26

Näytä artikkelit kohdesivustolla

Kun artikkelit on tallennettu site_articles-tauluun, kohdesivuston ei pidä yhdistää OxiRankeriin jokaista julkista näyttöä varten. Sen on luettava omasta tietokannastaan.

Blogin listasivu

SELECT
  id,
  lang,
  lang_label,
  dir,
  is_rtl,
  slug,
  title,
  meta_description,
  category,
  tags,
  reading_time_minutes,
  cover_image_url,
  og_image_url,
  source_created_at,
  published_at,
  created_at
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
ORDER BY COALESCE(source_created_at, published_at, created_at) DESC, id DESC
LIMIT 20 OFFSET 0;

Artikkelin yksityiskohtasivu

SELECT *
FROM public.site_articles
WHERE source_provider = 'oxiranker'
  AND lang = 'fa'
  AND slug = 'smart-internal-links'
  AND deleted_at IS NULL
  AND sync_status = 'synced'
  AND publish_status = 'published'
LIMIT 1;
Suositeltu artikkeli-URL
Monikielisillä sivustoilla voit käyttää mallia /{lang}/blog/{slug}. Esimerkki: /fa/blog/smart-internal-links
VAIHE 27

SEO, Schema, RTL ja HTML-renderöinti

Kohdesivuston on luettava SEO-data omasta tietokannastaan ja renderöitävä se artikkelisivulla.

SEO-kentät

title sivun otsikkoa varten
meta_description meta descriptionia varten
effective_canonical_url canonicalia varten
og_title ja og_description Open Graphia varten
og_image_url tai cover_image_url sosiaalisen esikatselun kuvaa varten
article_schema JSON-LD:tä varten

HTML-renderöinti

<div
  className="article-content"
  dangerouslySetInnerHTML={{ __html: article.html_body }}
/>

Jos kohdesivustolla on tiukka turvallisuuskäytäntö, se voi sanitizeoida HTML:n ennen tallennusta tai ennen näyttämistä.

RTL ja LTR

Jokaisella artikkelilla on kentät lang, dir ja is_rtl. Jos dir on rtl, artikkelisivu on renderöitävä oikealta vasemmalle.

<article dir="rtl">
  ...
</article>
VAIHE 28

Yleiset virheet ja niiden merkitykset

Kohdesivuston virheiden on oltava englanniksi, selkeitä ja ymmärrettäviä, jotta vianmääritys voidaan tehdä nopeasti.

Export API -virheet

Domainin export-token puuttuu.
Export API -tokenia ei lähetetty. Authorization: Bearer YOUR_EXPORT_TOKEN on lähetettävä.
Virheellinen domainin export-token.
Token on väärä, se on kierrätetty, se ei liity tähän domainiin tai domainId on väärä.
Tokenilla ei ole vaadittua scopea.
Tokenilla ei ole vaadittua scopea. Uusilla tokeneilla on oltava articles:read ja exports:read.
Virheellinen lang.
Query parameterissa oleva lang-arvo lähetettiin virheellisesti.

Webhook-virheet

Webhook URL:n on käytettävä HTTPS:ää.
Webhook URL:n on alettava https://.
Virheellinen Webhook-allekirjoitus.
Webhook Secret on väärä, secret on kierrätetty, raw body muuttui tai allekirjoitus laskettiin väärin.
Webhook timestamp on vanhentunut tai virheellinen.
Timestamp on vanha, tulevaisuudessa tai virheellinen. Tarkista palvelimen kello ja NTP.
Webhook-testitoimitus epäonnistui.
Kohdesivusto ei palauttanut onnistunutta 200–299-tilaa, aikakatkaisi tai ei käsitellyt webhook.testiä oikein.
VAIHE 29

Lopullinen API- ja Webhook-testauksen tarkistuslista

Toteutuksen jälkeen nämä testit varmistavat yhteyden alusta loppuun.

Export Token ja JSON -testi

GET https://oxiranker.com/api/v1/users/domains/1/export/articles?limit=2&offset=0
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json
GET https://oxiranker.com/api/v1/users/domains/1/articles/5/export/json
Authorization: Bearer YOUR_EXPORT_TOKEN
Accept: application/json

Webhook-testi

Jos kutsut kohde-Webhookia Postmanilla ilman allekirjoitusta, sinun on saatava turvavirhe. Tämä tarkoittaa, että endpoint on aktiivinen ja tarkistaa turvallisuuden.

{
  "error": "missing_webhook_signature",
  "message": "Webhook signature header is required."
}

Klikkaa sitten Test Webhook OxiRankerin sisällä. Jos vastaanotin tukee webhook.testiä, response_status-arvon on oltava 200.

Kierrätyksen jälkeen
Jos Webhook Secret tai Export Token kierrätetään, kopioi uusi arvo välittömästi, korvaa se kohdesivuston .env-tiedostossa ja käynnistä kohdebackend uudelleen tai redeployaa se.
VAIHE 30

Tärkeät säännöt oikeaan toteutukseen

Näitä sääntöjä on noudatettava API-integraatiossa, jotta järjestelmä pysyy turvallisena, vakaana ja ylläpidettävänä.

Export Token on tallennettava vain backendiin.
Webhook Secret on tallennettava vain backendiin.
Kohde-Webhookin endpointin on oltava täsmälleen /api/oxiranker/webhook.
Webhook URL:n on käytettävä HTTPS:ää.
Webhook signature ja timestamp on aina vahvistettava.
Artikkelit on tallennettava UPSERTilla, ei yksinkertaisella insertillä.
Duplikaattien estämiseen tarvitaan unique constraint.
Kuvat on ladattava OxiRankerista ja tallennettava kohdesivustolle.
Kohdesivusto ei saa pysyvästi riippua OxiRankerin kuva-URLeista.
Kuva-URLit on korvattava html_bodyssa ja article_schemassa.
Jos Webhook epäonnistuu, päivittäisen syncin on palautettava puuttunut artikkeli.
Käyttäjille tai lokeihin näytettävien virheiden on oltava englanniksi ja ymmärrettäviä.
Jos artikkeli on arkistoitu, automaattinen sync ei saa julkaista sitä uudelleen.
raw_detail_payload ja raw_list_item on säilytettävä, jotta alkuperäisen tulosteen dataa ei menetetä.
Jos Export Token tai Webhook Secret kierrätetään, uusi arvo on korvattava .env-tiedostossa ja backend on käynnistettävä uudelleen tai redeployattava.
VAIHE 31

Täydellinen API-virran yhteenveto

Kun nämä vaiheet on suoritettu, mukautettu verkkosivustosi voi turvallisesti vastaanottaa, tallentaa, mirrorata ja julkaista OxiRankerissa luotuja artikkeleita.

Rekisteröidy OxiRankeriin
Lisää domain
Vahvista domainin omistus
Valitse kielet ja lisää Blog URL
Lisää avainsanat jokaiselle kielelle
Täydennä Article Profile
Määritä Telegram tarvittaessa
Määritä automaattinen artikkelimoottori
Luo ensimmäinen artikkeli Generate-osiosta
Tarkista Preview ja SEO Report
Hanki Export Token API Outputista
Hanki valmiit Export API -endpointit
Hanki Webhook Secret
Luo kiinteä /api/oxiranker/webhook endpoint kohdebackendiin
Tallenna Token ja Secret kohdesivuston .env-tiedostoon
Luo site_articles-taulu ja unique constraint
Toteuta koko artikkelin haku Export JSONista
Toteuta Image Mirroring
Toteuta UPSERT
Toteuta päivittäinen sync
Renderöi artikkeli, SEO, OG ja Schema kohdetietokannasta
Suorita lopulliset Export API- ja Webhook-testit