Publique posts no Google e no Facebook

Publique anúncios, eventos e ofertas; agende um post em vários locais, confirme a publicação por site, gerencie posts de um local e reaja a webhooks.

Um post é um cartão com data no Google Business Profile ou na Página do Facebook de um local: um anúncio, um evento ou uma oferta. Uma chamada cria o post, a publicação ocorre em segundo plano e uma segunda chamada informa se cada site o aceitou.

Os três tipos de post usam POST /posts com o corpo encapsulado em input. Um corpo sem esse encapsulamento é rejeitado com 400. Os SDKs criam o encapsulamento e validam o payload antes do envio.

Campo inputFinalidade
postNameNome interno da campanha. Não é exibido aos clientes.
locationIdsIds dos locais em base64.
postTypeANNOUNCEMENT, EVENT ou OFFER.
postSitesGOOGLE, FACEBOOK ou ambos.
postMessage[]Um { site, message } por site em postSites.
postCta[]{ site, type, url } opcional. type é BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP ou GET_OFFER.
postMediaUrl[]{ site, url, type } opcional com type definido como IMAGE ou VIDEO.
postContextInfoDatas do evento ou detalhes da oferta. Obrigatório para EVENT e OFFER.

1. Publique um anúncio

Um anúncio recebe uma mensagem, um botão opcional e uma imagem opcional.

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

Guarde socialPost.id. status começa em INPROGRESS e postLinks fica vazio até que um site aceite o post; a etapa 5 consulta ambos.

2. Publique um evento

Um evento adiciona postContextInfo com title, startDay, endDay e horários de exibição opcionais. O Google rejeita um evento sem título, e os SDKs lançam ValidationError antes do envio se ele estiver ausente.

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

A resposta tem a mesma estrutura da etapa 1, com type: "EVENT" e as datas retornadas em socialPost.contextInfo. postContextInfo contém as datas do próprio evento; postScheduledDates (etapa 4) controla quando o post em si é publicado e retirado de publicação.

3. Publique uma oferta

O postContextInfo de uma oferta contém title, couponCode, discount, redeemUrl, termsConditions e o período de validade definido por startDay e endDay. Combine-o com um botão GET_OFFER no Facebook ou ORDER no Google; não existe um tipo 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." }    ]  }}'

A resposta retorna os campos da oferta em socialPost.contextInfo e define scheduledStartDate e scheduledEndDate com base no período de validade.

4. Envie um post para vários locais com agendamento

POST /bulk-posts recebe o mesmo input e envia uma campanha para cada id em locationIds. Adicione postScheduledDates com startDatetime e endDatetime no formato ISO-8601 para publicar mais tarde e retirar de publicação automaticamente; o post é então criado como SCHEDULED. Defina uma mensagem própria para cada site quando o tom for diferente.

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

O endpoint de operações em lote retorna a mesma estrutura de POST /posts, portanto a resposta também usa a chave createSocialPost, com status: "SCHEDULED" e ambos os ids em locationIds. Um problema de validação resulta em 200 com success: false e um array errors preenchido; os SDKs lançam ValidationError nesse caso. Consulte uma campanha em lote com GET /bulk-posts/{postId}, que adiciona o status por local.

5. Verifique o status de publicação e obtenha os links dos posts

GET /posts/{postId} retorna a campanha em socialPostView. Consulte-o periodicamente após a criação até que socialPostInfo.status deixe de ser INPROGRESS, depois leia publishDetails[] para obter o resultado de cada site e postLinks[] para obter as URLs publicadas. Faça as consultas em intervalos de dezenas de segundos; um 429 vem acompanhado de 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 }      ]    }  }}
CampoValoresSignificado
socialPostInfo.statusINPROGRESS, SCHEDULED, SUCCESS, EXPIREDA campanha como um todo. SUCCESS significa que a publicação terminou, não que todos os sites a aceitaram.
publishDetails[].statusPUBLISHING, PUBLISHED, ERRORUma entrada por site. Em caso de ERROR, submissionError informa o motivo.
postLinks[]{ site, link }A URL publicada por site, após a publicação.

views, clicks, reactions, shares e comments se acumulam no mesmo registro, discriminados por site em socialPostAnalyticsBySite. Os SDKs retornam socialPostInfo diretamente, por isso os exemplos leem post.status.

6. Liste as publicações de um local e exclua uma

GET /locations/{locationId}/posts é uma página com paginação por deslocamento (page, perPage até 50) de todas as campanhas que incluem o local, em postsByLocation com records[] e pageInfo.totalRecords. Passe o id em base64 sem alterações; não aplique codificação de URL a ele. Não há filtro por tipo, então selecione ofertas ou eventos de type no seu próprio código. As campanhas para vários locais da etapa 4 são listadas separadamente por GET /locations/{locationId}/bulk-posts.

DELETE /posts/{postId} remove a campanha daqui e de todos os sites em que ela foi publicada. Não é possível desfazer essa ação, então confirme primeiro o id na lista.

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"

Uma exclusão retorna data.deleteSocialPost com success: true e o socialPostId que foi removido. Excluir um id que já foi removido retorna, em vez disso, uma resposta errors com data: null, então é fácil detectar uma segunda exclusão.

7. Reaja aos webhooks de publicações

Com uma URL de webhook configurada em /dashboard/webhooks, quatro eventos cobrem o ciclo de vida da publicação, todos no payload padrão. Eles são disparados por local, não por campanha: uma publicação em massa para 20 locais produz 20 valores de post_item_id com um único post_id compartilhado.

EventoDisparado quandoCampo data adicional
local_post.createdO item está na fila para o Google. Ainda não está no ar.
local_post.publishedO Google aceitou o item e ele está no ar.post_external_id, o nome do recurso no Google.
local_post.rejectedO Google rejeitou o item ou o envio falhou.rejection_reason.
local_post.deletedO item foi excluído.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 geralmente está ausente, então continue consultando periodicamente conforme a etapa 5 para obter postLinks se precisar de um link clicável. Responda com 200 em até 5 segundos; não há novas tentativas. O receptor e a verificação de assinatura estão em Reaja a eventos com webhooks.

Próximas etapas