Publikasikan postingan ke Google dan Facebook

Publikasikan pengumuman, acara, penawaran; jadwalkan satu pos di banyak lokasi, cek tayang di tiap situs, kelola pos lokasi, dan tanggapi webhook pos.

Postingan adalah kartu bertanggal di Google Business Profile atau Halaman Facebook suatu lokasi: pengumuman, acara, atau penawaran. Satu panggilan membuatnya, publikasi berjalan di latar belakang, dan panggilan kedua memberi tahu Anda apakah setiap situs menerimanya.

Ketiga jenis postingan menggunakan POST /posts dengan body yang dibungkus dalam input. Body tanpa pembungkus ditolak dengan 400. SDK membuat pembungkus dan memvalidasi payload sebelum mengirimkannya.

Field inputKegunaan
postNameNama kampanye internal. Tidak ditampilkan kepada pelanggan.
locationIdsId lokasi dalam base64.
postTypeANNOUNCEMENT, EVENT, atau OFFER.
postSitesGOOGLE, FACEBOOK, atau keduanya.
postMessage[]Satu { site, message } per situs dalam postSites.
postCta[]{ site, type, url } opsional. type bernilai BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, atau GET_OFFER.
postMediaUrl[]{ site, url, type } opsional dengan type bernilai IMAGE atau VIDEO.
postContextInfoTanggal acara atau detail penawaran. Wajib untuk EVENT dan OFFER.

1. Publikasikan pengumuman

Pengumuman menerima pesan, tombol opsional, dan gambar opsional.

Announcement
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Grand Opening Announcement",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "ANNOUNCEMENT",    "postSites": ["GOOGLE"],    "postMessage": [      { "site": "GOOGLE", "message": "We are now open! Visit us this week for our grand opening." }    ],    "postCta": [      { "site": "GOOGLE", "type": "LEARN_MORE", "url": "https://acmekitchen.example.com/opening" }    ],    "postMediaUrl": [      { "site": "GOOGLE", "url": "https://cdn.example.com/opening.jpg", "type": "IMAGE" }    ]  }}'
JSON
{  "data": {    "createSocialPost": {      "success": true,      "errors": [],      "socialPost": {        "id": "U29jaWFsUG9zdDo0NDEyMg==",        "name": "Grand Opening Announcement",        "status": "INPROGRESS",        "type": "ANNOUNCEMENT",        "sites": ["GOOGLE"],        "publishDetails": [          { "site": "GOOGLE", "status": "PUBLISHING", "createdDate": "2026-06-28", "submissionError": null }        ],        "postLinks": [],        "locationIds": [1800289]      }    }  }}

Simpan socialPost.id. status awalnya bernilai INPROGRESS dan postLinks kosong hingga suatu situs menerima postingan; langkah 5 membaca kembali keduanya.

2. Publikasikan acara

Acara menambahkan postContextInfo dengan title, startDay, endDay, dan waktu tampilan opsional. Google menolak acara tanpa judul, dan SDK memunculkan ValidationError sebelum mengirim jika judul tidak ada.

Event
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Summer Sale Kickoff Event",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "EVENT",    "postSites": ["GOOGLE"],    "postContextInfo": {      "title": "Summer Sale Kickoff",      "startDay": "2026-07-15",      "startTime": "10:00am",      "endDay": "2026-07-15",      "endTime": "06:00pm"    },    "postMessage": [      { "site": "GOOGLE", "message": "Join us for the Summer Sale kickoff: refreshments, giveaways, and up to 40% off in store." }    ]  }}'

Respons memiliki struktur yang sama dengan langkah 1, dengan type: "EVENT" dan tanggal yang dikembalikan di dalam socialPost.contextInfo. postContextInfo menyimpan tanggal acara itu sendiri; postScheduledDates (langkah 4) mengontrol kapan postingan itu sendiri dipublikasikan dan ditarik dari publikasi.

3. Publikasikan penawaran

postContextInfo pada penawaran memuat title, couponCode, discount, redeemUrl, termsConditions, serta rentang masa berlaku startDay dan endDay. Pasangkan dengan tombol GET_OFFER di Facebook atau ORDER di Google; tidak ada jenis REDEEM.

Offer
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Summer 25% Off",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "OFFER",    "postSites": ["GOOGLE"],    "postContextInfo": {      "title": "25% off storewide",      "couponCode": "SUMMER25",      "discount": "25%",      "redeemUrl": "https://acmekitchen.example.com/summer",      "termsConditions": "Valid in store only through July 6. Cannot be combined with other offers.",      "startDay": "2026-06-22",      "endDay": "2026-07-06"    },    "postMessage": [      { "site": "GOOGLE", "message": "Celebrate with us: 25% off everything in store this week." }    ]  }}'

Respons mengembalikan field penawaran di dalam socialPost.contextInfo dan menetapkan scheduledStartDate serta scheduledEndDate berdasarkan rentang masa berlaku.

4. Kirim satu postingan ke banyak lokasi sesuai jadwal

POST /bulk-posts menerima input yang sama dan mengirim satu kampanye ke setiap id dalam locationIds. Tambahkan postScheduledDates dengan startDatetime dan endDatetime dalam format ISO-8601 untuk memublikasikan nanti dan menariknya dari publikasi secara otomatis; postingan kemudian dibuat sebagai SCHEDULED. Berikan pesan tersendiri untuk setiap situs jika gaya bahasanya berbeda.

Bulk post
curl -X POST https://listingsapi.com/api/v4/bulk-posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "July Reopening Announcement",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ==", "TG9jYXRpb246MTgwMDI5MA=="],    "postType": "ANNOUNCEMENT",    "postSites": ["GOOGLE", "FACEBOOK"],    "postMessage": [      { "site": "GOOGLE", "message": "We are back! Visit us this week for our summer menu." },      { "site": "FACEBOOK", "message": "We are back! Swing by for the new summer menu." }    ],    "postScheduledDates": {      "startDatetime": "2026-07-10T09:00:00Z",      "endDatetime": "2026-07-24T23:59:00Z"    }  }}'

Endpoint massal mengembalikan struktur yang sama dengan POST /posts, sehingga respons juga menggunakan kunci createSocialPost, dengan status: "SCHEDULED" dan kedua id di dalam locationIds. Masalah validasi mengembalikan 200 dengan success: false dan array errors yang terisi; SDK memunculkan ValidationError untuk masalah tersebut. Baca kembali kampanye massal dengan GET /bulk-posts/{postId}, yang menambahkan status per lokasi.

5. Periksa status publikasi dan kumpulkan tautan postingan

GET /posts/{postId} mengembalikan kampanye di dalam socialPostView. Lakukan polling setelah pembuatan hingga socialPostInfo.status tidak lagi bernilai INPROGRESS, lalu baca publishDetails[] untuk hasil setiap situs dan postLinks[] untuk URL postingan yang sudah tayang. Beri jeda puluhan detik di antara polling; 429 disertai Retry-After.

Post status
curl https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "socialPostView": {      "socialPostInfo": {        "id": "U29jaWFsUG9zdDo0NDEyMg==",        "status": "SUCCESS",        "views": 128,        "clicks": 14,        "publishDetails": [          { "site": "GOOGLE", "status": "PUBLISHED", "publishedDate": "2026-06-28", "submissionError": null }        ],        "postLinks": [          { "site": "GOOGLE", "link": "https://www.google.com/maps/place/?post=44122" }        ]      },      "socialPostAnalyticsBySite": [        { "site": "GOOGLE", "views": 128, "clicks": 14, "reactions": 3, "shares": 0, "comments": 1 }      ]    }  }}
FieldNilaiArti
socialPostInfo.statusINPROGRESS, SCHEDULED, SUCCESS, EXPIREDKampanye secara keseluruhan. SUCCESS berarti publikasi telah selesai, bukan berarti setiap situs menerimanya.
publishDetails[].statusPUBLISHING, PUBLISHED, ERRORSatu entri per situs. Jika berstatus ERROR, submissionError menjelaskan alasannya.
postLinks[]{ site, link }URL postingan yang sudah tayang per situs, setelah dipublikasikan.

views, clicks, reactions, shares, dan comments terakumulasi pada rekaman yang sama, dengan perincian per situs di dalam socialPostAnalyticsBySite. SDK mengembalikan socialPostInfo secara langsung, sehingga contoh membaca post.status.

6. Tampilkan daftar postingan suatu lokasi dan hapus salah satunya

GET /locations/{locationId}/posts adalah halaman berbasis offset (page, perPage hingga 50) yang memuat setiap kampanye yang menyertakan lokasi tersebut, di bawah postsByLocation dengan records[] dan pageInfo.totalRecords. Teruskan id base64 tanpa perubahan; jangan lakukan pengodean URL pada id tersebut. Tidak ada filter jenis, jadi pilih penawaran atau acara dari type dalam kode Anda sendiri. Kampanye multilokasi dari langkah 4 dicantumkan secara terpisah oleh GET /locations/{locationId}/bulk-posts.

DELETE /posts/{postId} menghapus kampanye di sini dan menariknya dari setiap situs tempat kampanye tersebut telah dipublikasikan. Penghapusan tidak dapat dibatalkan, jadi pastikan id dari daftar terlebih dahulu.

List and delete
curl "https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/posts?page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY" curl -X DELETE https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"

Operasi penghapusan mengembalikan data.deleteSocialPost dengan success: true dan socialPostId yang dihapusnya. Menghapus id yang sudah tidak ada akan mengembalikan respons errors dengan data: null sebagai gantinya, sehingga penghapusan kedua mudah dideteksi.

7. Tanggapi webhook postingan

Dengan URL webhook yang ditetapkan di /dashboard/webhooks, empat peristiwa mencakup siklus hidup postingan, semuanya menggunakan payload standar. Peristiwa ini dipicu per lokasi, bukan per kampanye: postingan massal ke 20 lokasi menghasilkan 20 nilai post_item_id dengan satu post_id yang sama.

PeristiwaDipicu ketikaField data tambahan
local_post.createdItem masuk antrean untuk Google. Belum tayang.
local_post.publishedGoogle telah menerimanya dan item tersebut sudah tayang.post_external_id, nama resource Google.
local_post.rejectedGoogle menolaknya atau pengiriman gagal.rejection_reason.
local_post.deletedItem telah dihapus.post_external_id.
Route local_post events
function handle(payload: { event: string; location_id: string; data: Record<string, unknown> }) {if (!payload.event.startsWith('local_post.')) return; const key = String(payload.data.post_item_id); // one row per locationswitch (payload.event) {  case 'local_post.published':    markLive(key, payload.location_id, payload.data.post_external_id);    break;  case 'local_post.rejected':    markRejected(key, payload.location_id, payload.data.rejection_reason);    break;  case 'local_post.deleted':    markDeleted(key);    break;}}

published_url biasanya tidak ada, jadi terus lakukan polling pada langkah 5 untuk mendapatkan postLinks jika Anda memerlukan tautan yang dapat diklik. Berikan respons 200 dalam waktu 5 detik; tidak ada percobaan ulang. Penerima dan pemeriksaan tanda tangan dijelaskan di Tanggapi peristiwa dengan webhook.

Langkah selanjutnya