向 Google 和 Facebook 发布帖子

发布公告、活动和优惠,安排一个帖子在多个地点定时发布,确认它已在每个站点上线,管理地点的帖子,并响应帖子 webhook。

帖子是地点的 Google Business Profile 或 Facebook 公共主页上带有日期的卡片,可以是公告、活动或优惠。调用一次即可创建帖子,发布过程在后台运行,再调用一次即可了解每个站点是否接受了该帖子。

所有 3 种帖子类型都使用 POST /posts,并将请求体封装在 input 中。未封装的请求体会被拒绝,并返回 400。SDK 会构建封装结构,并在发送前验证载荷。

input 字段用途
postName内部营销活动名称。不向客户显示。
locationIdsBase64 地点 id。
postTypeANNOUNCEMENT、EVENT 或 OFFER。
postSitesGOOGLE、FACEBOOK 或两者。
postMessage[]postSites 中的每个站点各有一个 { site, message }。
postCta[]可选的 { site, type, url }。type 为 BOOK、ORDER、SHOP、LEARN_MORE、SIGN_UP 或 GET_OFFER。
postMediaUrl[]可选的 { site, url, type },其 type 为 IMAGE 或 VIDEO。
postContextInfo活动日期或优惠详情。EVENT 和 OFFER 必填。

1. 发布公告

公告包含消息正文、可选的按钮和可选的图片。

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]      }    }  }}

请保存 socialPost.id。status 的初始值为 INPROGRESS,在有站点接受帖子之前,postLinks 为空;步骤 5 会读取这两项。

2. 发布活动

活动会添加 postContextInfo,其中包含 title、startDay、endDay 以及可选的显示时间。Google 会拒绝没有标题的活动,如果缺少标题,SDK 会在发送前抛出 ValidationError。

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." }    ]  }}'

响应的结构与步骤 1 相同,包含 type: "EVENT",日期则在 socialPost.contextInfo 下返回。postContextInfo 保存活动本身的日期;postScheduledDates(步骤 4)控制帖子本身的发布时间和下线时间。

3. 发布优惠

优惠的 postContextInfo 包含 title、couponCode、discount、redeemUrl、termsConditions,以及有效期 startDay 和 endDay。在 Facebook 上为其搭配 GET_OFFER 按钮,在 Google 上则搭配 ORDER 按钮;不存在 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." }    ]  }}'

响应在 socialPost.contextInfo 下返回优惠字段,并根据有效期设置 scheduledStartDate 和 scheduledEndDate。

4. 将一个帖子定时发送到多个地点

POST /bulk-posts 接受相同的 input,并将一个营销活动发送到 locationIds 中的每个 id。添加包含 ISO-8601 格式的 startDatetime 和 endDatetime 的 postScheduledDates,即可稍后发布并自动下线;此时,帖子会以 SCHEDULED 状态创建。如果不同站点需要不同的语气,请为每个站点分别提供消息正文。

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"    }  }}'

批量端点返回的结构与 POST /posts 相同,因此响应也以 createSocialPost 为键,其中包含 status: "SCHEDULED",而 locationIds 中包含两个 id。如果验证出现问题,会返回 200,其中包含 success: false 和非空的 errors 数组;SDK 会为此抛出 ValidationError。使用 GET /bulk-posts/{postId} 读取批量营销活动,返回结果中会增加每个地点的状态。

5. 检查发布状态并收集帖子链接

GET /posts/{postId} 在 socialPostView 下返回营销活动。创建后,轮询此端点,直到 socialPostInfo.status 不再为 INPROGRESS,然后读取 publishDetails[] 以获取每个站点的结果,并读取 postLinks[] 以获取已上线帖子的 URL。轮询间隔应为几十秒;429 会附带 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 }      ]    }  }}
字段值含义
socialPostInfo.statusINPROGRESS, SCHEDULED, SUCCESS, EXPIRED整个营销活动的状态。SUCCESS 表示发布已完成,并不表示每个站点都接受了该帖子。
publishDetails[].statusPUBLISHING, PUBLISHED, ERROR每个站点对应一个条目。当状态为 ERROR 时,submissionError 会说明原因。
postLinks[]{ site, link }发布后,每个站点上已上线帖子的 URL。

views、clicks、reactions、shares 和 comments 会在同一条记录中累积,并在 socialPostAnalyticsBySite 中按站点细分。SDK 会直接返回 socialPostInfo,因此示例中读取的是 post.status。

6. 列出地点的帖子并删除其中一条

GET /locations/{locationId}/posts 以偏移量分页(page,perPage 最大为 50)返回包含该地点的所有营销活动,位于 postsByLocation 下,包含 records[] 和 pageInfo.totalRecords。请原样传递 base64 id;不要做 URL 编码。没有类型筛选器,因此请在您自己的代码中从 type 中筛选出优惠或活动。步骤 4 中的多地点营销活动由 GET /locations/{locationId}/bulk-posts 单独列出。

DELETE /posts/{postId} 会删除此处的营销活动,并将其从所有已发布到的网站上移除。此操作无法撤销,因此请先核对列表中的 id。

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"

删除操作返回 data.deleteSocialPost,其中包含 success: true 和已删除的 socialPostId。删除已不存在的 id 时,则会返回包含 data: null 的 errors 响应,因此很容易识别重复删除。

7. 处理帖子 webhook

在 /dashboard/webhooks 设置 webhook URL 后,4 种事件涵盖帖子的整个生命周期,均使用标准载荷。这些事件按地点触发,而非按营销活动触发:向 20 个地点批量发布帖子会产生 20 个 post_item_id 值,并共用 1 个 post_id。

事件触发时机额外的 data 字段
local_post.created该条目已加入提交至 Google 的队列。尚未上线。
local_post.publishedGoogle 已接受该条目,且该条目已上线。post_external_id,Google 的资源名称。
local_post.rejectedGoogle 拒绝了该条目,或提交失败。rejection_reason.
local_post.deleted该条目已被删除。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 通常不存在,因此,如果您需要可点击的链接,请按照步骤 5 持续轮询以获取 postLinks。请在 5 秒内返回 200;系统不会重试。接收端和签名校验的说明见 通过 webhook 响应事件。

后续步骤