Webhooks

Parkdly can send real-time notifications to your application when courses are published or deleted.

Setup

Contact Parkdly to configure your webhook endpoint. Provide:

  • Webhook URL - HTTPS endpoint to receive events (required in production)
  • Webhook Secret - Optional secret for signature verification

πŸ“§ Email info@parkdly.com to configure webhooks.

Events

course.published

Triggered when a user publishes a linked course in Parkdly Studio.

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

Triggered when a user deletes a linked course in Parkdly Studio. The payload contains the course data as it was before deletion, allowing your application to identify and clean up 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: The course is already deleted in Parkdly when this webhook fires. Use externalCourseId to identify and remove the corresponding resource in your system.

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 to ensure 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 should respond with 2xx status code within 10 seconds.

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

Retries

Failed deliveries are automatically retried:

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

Best Practices

  • βœ… Respond quickly (under 10 seconds)
  • βœ… Always verify signatures in production
  • βœ… Use HTTPS endpoints only
  • βœ… Process events asynchronously
  • βœ… Return 2xx even if processing fails (handle errors internally)
  • βœ… Log delivery IDs for debugging

Next Steps