Webhooks

Parkdly sends a notification to your application when a course is published or deleted.

Setup

Contact Parkdly to configure your webhook endpoint. Send us:

  • Webhook URL - The HTTPS endpoint that receives events. It is required in production.
  • Webhook Secret - A secret for signature verification. It is optional.

📧 Email info@parkdly.com to configure webhooks.

Events

course.published

Sent when a user publishes a linked course in Parkdly Studio. It is also sent when a user restores a deleted course: the course comes back with the same courseId, hole ids and externalCourseId, so treat it as published again.

Payload:

{
  "event": "course.published",
  "timestamp": "2025-01-15T12:00:00Z",
  "courseId": "course_123",
  "externalCourseId": "your-app-course-id",
  "course": {
    "id": "course_123",
    "externalCourseId": "your-app-course-id",
    "name": "Example DGC",
    "location": {
      "lat": 60.1699,
      "lng": 24.9384
    },
    "studioUrl": "/studio/destinations/dest_456/courses/course_123",
    "fairways": [
      {
        "id": "fairway_1",
        "number": 1,
        "par": 3,
        "distance": 100,
        "teePosition": {
          "lat": 60.1699,
          "lng": 24.9384
        },
        "targetPosition": {
          "lat": 60.17,
          "lng": 24.94
        },
        "flightPath": [
          {
            "order": 0,
            "lat": 60.1699,
            "lng": 24.9384
          },
          {
            "order": 1,
            "lat": 60.16995,
            "lng": 24.9392
          },
          {
            "order": 2,
            "lat": 60.17,
            "lng": 24.94
          }
        ]
      }
    ],
    "createdAt": "2025-01-15T10:00:00Z",
    "updatedAt": "2025-01-15T12:00:00Z"
  }
}

The course object follows the same structure as the API Endpoints Course response.

course.deleted

Sent when a user deletes a linked course in Parkdly Studio, and once for each linked course when a user deletes their Parkdly account. The payload has the course data from before the deletion. Your application can use it to find and remove the corresponding resource.

Payload:

{
  "event": "course.deleted",
  "timestamp": "2025-01-15T14:00:00Z",
  "courseId": "course_123",
  "externalCourseId": "your-app-course-id",
  "course": {
    "id": "course_123",
    "externalCourseId": "your-app-course-id",
    "name": "Example DGC",
    "location": {
      "lat": 60.1699,
      "lng": 24.9384
    },
    "studioUrl": "/studio/destinations/dest_456/courses/course_123",
    "fairways": [
      {
        "id": "fairway_1",
        "number": 1,
        "par": 3,
        "distance": 100,
        "teePosition": {
          "lat": 60.1699,
          "lng": 24.9384
        },
        "targetPosition": {
          "lat": 60.17,
          "lng": 24.94
        },
        "flightPath": [
          {
            "order": 0,
            "lat": 60.1699,
            "lng": 24.9384
          },
          {
            "order": 1,
            "lat": 60.16995,
            "lng": 24.9392
          },
          {
            "order": 2,
            "lat": 60.17,
            "lng": 24.94
          }
        ]
      }
    ],
    "createdAt": "2025-01-15T10:00:00Z",
    "updatedAt": "2025-01-15T12:00:00Z"
  }
}

Note: Parkdly has already deleted the course when it sends this webhook. Use externalCourseId to find and remove the corresponding resource in your system. A user can restore a deleted course for 30 days; if they do, you receive course.published for it.

Request Headers

Content-Type: application/json
User-Agent: Parkdly-Webhooks/1.0
X-Parkdly-Event: course.published | course.deleted
X-Parkdly-Delivery: unique-delivery-id
X-Parkdly-Signature: hmac-sha256-signature (if webhook secret configured)

Signature Verification

If you configured a webhook secret, verify the signature. This confirms that the request came from Parkdly:

const crypto = require('crypto')

function verifySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex')

  // Use timingSafeEqual to prevent timing attacks
  const sigBuffer = Buffer.from(signature)
  const expectedBuffer = Buffer.from(expectedSignature)
  return sigBuffer.length === expectedBuffer.length &&
    crypto.timingSafeEqual(sigBuffer, expectedBuffer)
}

// Usage
const signature = req.headers['x-parkdly-signature']
const isValid = verifySignature(
  JSON.stringify(req.body),
  signature,
  process.env.WEBHOOK_SECRET
)

Response

Your endpoint must respond with a 2xx status code within 10 seconds.

Recommended response: 200 OK with an empty body or simple JSON.

Retries

Parkdly retries failed deliveries automatically:

  • Attempt 1: Immediate
  • Attempt 2: After 5 minutes
  • Attempt 3: After 15 minutes
  • Attempt 4: After 1 hour (final attempt)

Parkdly checks for retries every 5 minutes. A retry can arrive a few minutes later than the times above. Make your endpoint idempotent, because the same delivery can arrive more than once.

When deliveries stop

Parkdly sends webhooks, and retries them, only while your app is active, has a webhook URL with webhooks turned on, and the user still has the app connected. If a user disconnects your app, or your app is suspended or revoked, new deliveries are skipped and any pending retries are marked failed and not sent.

Best Practices

  • ✅ Respond in less than 10 seconds.
  • ✅ Verify signatures in production.
  • ✅ Use HTTPS endpoints only.
  • ✅ Process events asynchronously.
  • ✅ Return 2xx even if processing fails. Handle errors in your own system.
  • ✅ Log delivery IDs to find problems.

Next Steps