---
title: "Add a webhook endpoint"
method: POST
path: "/webhooks"
tags: ["Webhooks"]
---

# Add a webhook endpoint

`POST /webhooks`

## Request

- Base URL: `https://api.gmaps.dev/v1`
- URL: `https://api.gmaps.dev/v1/webhooks`
- Auth: HTTP bearer

Tell us where to POST when a collection ends, instead of asking us every few seconds whether it is done. The answer shows the signing secret once and never again. Store it, then check every request against it. The body carries counts and a link to the places, never the places themselves. Needs an API key and a paid plan. Costs no credits.

## Request body

- object
  - `url` string, uri, required: Where to POST the event. Must start with https:// and resolve to a public address. We will not send your results unencrypted or into a private network.
  - `events` string[]: Which events to send here. "collection.completed" fires when a collection ends, even one that found no places; "collection.failed" fires when an error stops it. A collection you cancel sends neither.
  - `description` string: Your own note about this endpoint, such as which of your services it is.

## Response `201`

Add a webhook endpoint

- object
  - `data` object, required
    - `id` string, uuid, required: Our id for the endpoint. update_webhook, delete_webhook, test_webhook and list_webhook_deliveries take it.
    - `url` string, required: Where we POST events.
    - `events` string[], required: The events we send here.
    - `description` string, nullable, required: Your own note about this endpoint, such as which of your services it is.
    - `enabled` boolean, required: False when you or we switched it off. We send nothing to it then.
    - `disabledReason` string, nullable, required: Why we stopped sending here, in English, with the last failure. Null while it works, and when you switched it off yourself.
    - `disabledReasonKey` 'serverErrors.validation' | 'serverErrors.unauthorized' | 'serverErrors.keyInactive' | 'serverErrors.signInRequired' | 'serverErrors.planGateEmails' | 'serverErrors.planGateWebhooks' | 'serverErrors.planGateContact' | 'serverErrors.aupRequired' | 'serverErrors.forbidden' | 'serverErrors.accountSuspended' | 'serverErrors.accountDeletionRequested' | 'serverErrors.accountDeleted' | 'serverErrors.lastStaff' | 'serverErrors.notFound' | 'serverErrors.placeNotFound' | 'serverErrors.placeShortLink' | 'serverErrors.placeLinkNotOne' | 'serverErrors.placeRefUnreadable' | 'serverErrors.collectionNotFound' | 'serverErrors.collectionEnded' | 'serverErrors.collectionPlacesDeleted' | 'serverErrors.exportNotFound' | 'serverErrors.exportNothingYet' | 'serverErrors.exportNothingFound' | 'serverErrors.conflict' | 'serverErrors.payloadTooLarge' | 'serverErrors.geoUnresolved' | 'serverErrors.cityAmbiguous' | 'serverErrors.cityDidYouMean' | 'serverErrors.cityUnknown' | 'serverErrors.cityIbgeUnknown' | 'serverErrors.areaTooLarge' | 'serverErrors.radiusTooLarge' | 'serverErrors.noSearchTerms' | 'serverErrors.tooManyTerms' | 'serverErrors.maxPlacesOverPlan' | 'serverErrors.xlsxTooLarge' | 'serverErrors.keyLimit' | 'serverErrors.subscriptionOpen' | 'serverErrors.checkoutOpen' | 'serverErrors.webhookNotHttps' | 'serverErrors.webhookCredentials' | 'serverErrors.webhookPrivateAddress' | 'serverErrors.webhookNoAddress' | 'serverErrors.webhookDuplicate' | 'serverErrors.webhookUnreachable' | 'serverErrors.webhookTimeout' | 'serverErrors.webhookStatus' | 'serverErrors.webhookRedirect' | 'serverErrors.webhookStopped' | 'serverErrors.webhookOff' | 'serverErrors.upstreamBlocked' | 'serverErrors.rateLimit' | 'serverErrors.quotaMonth' | 'serverErrors.quotaDay' | 'serverErrors.concurrency' | 'serverErrors.internal' | 'serverErrors.engineUnavailable' | 'serverErrors.collectionEngineFailed' | 'serverErrors.serviceUnavailable' | 'serverErrors.billingUnavailable' | 'serverErrors.billingCurrencyUnavailable', nullable, required: The same reason as a `messageKey`, to show it in your reader's language, with `disabledReasonParams` filling its blanks. When it is null, show `disabledReason`.
    - `disabledReasonParams` object, nullable, required: The values that fill `disabledReasonKey`'s blanks, such as how many deliveries failed in a row. Null when there are none.
    - `consecutiveFailures` integer, required: Deliveries that failed in a row, after all their tries. Back to zero on the next one that works.
    - `lastSuccessAt` string, date-time, nullable, required: When a delivery last arrived. Null until one does.
    - `lastFailureAt` string, date-time, nullable, required: When a try last failed. Null if none has.
    - `createdAt` string, date-time, required: When you added the endpoint.
    - `secret` string, starts_with, nullable, required: The signing secret, shown once, when you add the endpoint, and never again. Store it: it proves a request came from us. Null on every later read.
  - `meta` union
    - object
      - `total` integer, required: Every item that matches, not just this page.
      - `limit` integer, required: The most items a page holds.
      - `offset` integer, required: How many items came before this page.
      - `hasMore` boolean, required: True when another page follows.
    - object
      - `cost` integer, required: Credits this call took.
      - `creditsRemaining` integer, required: Credits left after this call, this month's and bought ones together.
      - `requestId` string, required: This call's id. Send it to support if something went wrong.
      - `cached` boolean: True when this response used only saved results.

## Other responses

- `default`: Any other failure.

---

[API](https://skmtc.dev/gmaps/apis/gmaps-dev.md) · [All operations](https://skmtc.dev/gmaps/apis/gmaps-dev/llms.txt) · [OpenAPI document](https://skmtc.dev/gmaps/apis/gmaps-dev/revisions/0b3459aaf802?raw)
