Przejdź do głównej zawartości

Using Webhooks in Gridaly

Napisane przez Patryk Karawajski

1. What are Webhooks?

Webhooks are a powerful tool that allows Gridaly to send real-time notifications to your external systems whenever specific events occur within your organization or events managed on the Gridaly platform.

Instead of you constantly asking Gridaly if something new happened (polling), Gridaly will automatically send an HTTP POST request containing relevant data to a URL you specify (your "webhook endpoint") as soon as an event happens.

Common Use Cases:

  • Syncing attendee registration data with your CRM.

  • Updating external databases when tickets are sold or attendees check in.

  • Triggering custom workflows in marketing automation tools.

  • Building custom dashboards with real-time event data.

  • Integrating with accounting software upon invoice generation.

2. Creating and Configuring a Webhook

You can manage webhooks within your Organization settings in the Gridaly Admin panel.

When creating or editing a webhook, you'll configure the following:

  • Name: A descriptive name for your webhook (e.g., "CRM Attendee Sync").

  • Description (Optional): More details about the webhook's purpose.

  • URL: The HTTPS endpoint URL of your external application that will receive the webhook notifications from Gridaly. This endpoint must be publicly accessible.

  • Active: A toggle to enable or disable the webhook. Disabled webhooks will not receive any notifications.

  • Event Types: Select the specific events within Gridaly that should trigger this webhook. Examples include attendee.created, attendee.updated, attendee.deleted, etc. You can select multiple event types. See section 7 for the full list of available event types.

  • Events (Optional): By default, a webhook applies to all events within your organization. You can optionally select specific events if you only want the webhook to trigger for those particular events.

  • Authentication Type: Choose how Gridaly should authenticate when sending requests to your URL:

    • None: No authentication is sent. (Not recommended unless your endpoint is secured by other means).

    • Header: Gridaly will include a custom HTTP header.

    • Basic Auth: Gridaly will use HTTP Basic Authentication.

    • Bearer Token: Gridaly will include an Authorization: Bearer YOUR_TOKEN header.

    • Query String: Gridaly will append the authentication details as query parameters to the URL.

  • Authentication Token: The actual secret/token/credentials corresponding to the selected Authentication Type. The required format depends on the selected type:

    • For Header: Enter the header name and value separated by a colon (:). Example: X-API-Key: yourSecretValue123

    • For Basic Auth: Enter the username and password separated by a colon (:). Example: myUsername:myPassword

    • For Bearer Token: Enter only the token value itself. Example: yourBearerTokenValue

    • For Query String: Enter the query parameter key and value. Example: apiKey=yourSecretKey (Do not include the ? or &, Gridaly adds those automatically).

  • Secret Key: This key is automatically generated by Gridaly when you create the webhook. It's crucial for verifying the authenticity of incoming requests. You will need to copy this key and store it securely in your receiving application. Do not share this key.

  • Retry Count: The maximum number of times Gridaly will attempt to resend a notification if the initial delivery to your URL fails (e.g., due to a temporary network issue or an error response from your server). Retries use an exponential backoff strategy (increasing delays between attempts).

3. Understanding the Payload

When a selected event occurs, Gridaly sends an HTTP POST request to your configured URL with a Content-Type: application/json header. The JSON body of the request follows this structure:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

// UUID of the primary resource related to the event "event": "attendee.created",

// The type of event that occurred "data":

{

// Object containing detailed information about the event and resource

"id": "123e4567-e89b-12d3-a456-426614174000",

"firstname": "John",

"surname": "Doe",

// ... other relevant attendee fields

}

}

  • id: The unique identifier (UUID) of the main Gridaly resource involved in the event (e.g., the Attendee's UUID for attendee.created).

  • event: A string indicating the type of event that triggered the webhook (matches one of the types you subscribed to).

  • data: An object containing the specific details related to the event. The structure of this object varies depending on the event type.

4. Securing Your Endpoint (Signature Verification)

To ensure that incoming requests genuinely originate from Gridaly and haven't been tampered with, you must verify the signature included in each request.

Gridaly includes the following HTTP headers in every webhook request:

  • X-Gridaly-Signature: An HMAC-SHA256 signature generated using your webhook's Secret Key and the raw JSON payload of the request.

  • X-Gridaly-Timestamp: The Unix timestamp when the request was sent.

  • X-Gridaly-Event: The event type string (e.g., attendee.created).

  • X-Gridaly-Delivery: A unique identifier (UUID) for this specific delivery attempt.

  • User-Agent: Always set to Gridaly-Webhooks/1.0.

Verification Process:

  1. Extract Headers: Get the values of X-Gridaly-Signature and X-Gridaly-Timestamp from the incoming request headers.

  2. Check Timestamp (Optional but Recommended): Compare the X-Gridaly-Timestamp with the current time on your server. Reject requests that are too old (e.g., older than 5 minutes) to prevent replay attacks.

  3. Get Raw Payload: Access the raw, unparsed JSON body of the incoming POST request.

  4. Compute Signature: Calculate the HMAC-SHA256 hash of the raw JSON payload using the Secret Key associated with the webhook in your Gridaly settings.

  5. Compare Signatures: Compare the signature you computed in step 4 with the signature received in the X-Gridaly-Signature header. Use a timing-safe comparison function if possible.

  6. Accept or Reject: If the signatures match (and the timestamp is valid), the request is authentic. Process the payload. If they don't match, reject the request (e.g., respond with HTTP status 400 or 401) and log the incident. Do not process the payload.

Pseudo-code Example (Conceptual):

// Your webhook endpoint handler function

function handleGridalyWebhook(request) {

const receivedSignature = request.headers['X-Gridaly-Signature'];

const receivedTimestamp = request.headers['X-Gridaly-Timestamp'];

const rawPayload = request.rawBody; // Access the raw request body

// 1. Get your stored Secret Key for this webhook

const secretKey = getSecretKeyFromYourConfig();

// 2. (Optional) Check timestamp tolerance

const tolerance = 5 * 60; // 5 minutes in seconds

const currentTimestamp = Math.floor(Date.now() / 1000);

if (Math.abs(currentTimestamp - receivedTimestamp) > tolerance) {

console.error("Timestamp validation failed");

return response.status(400).send("Timestamp validation failed");

}

// 3. Compute the expected signature

const expectedSignature = computeHmacSha256(rawPayload, secretKey);

// 4. Compare signatures securely

if (secureCompare(receivedSignature, expectedSignature)) {

// Signatures match - process the payload

const payload = JSON.parse(rawPayload);

processEvent(payload);

return response.status(200).send("OK");

} else {

// Signatures do NOT match - reject the request

console.error("Signature validation failed");

return response.status(401).send("Signature validation failed");

}

}

// Helper function for HMAC-SHA256

// (implementation depends on your language/framework)

function computeHmacSha256(data, secret) {

// ... use crypto library to compute HMAC-SHA256 hash ...

return computedHash;

}

// Helper function for timing-safe comparison

// (implementation depends on your language/framework)

function secureCompare(a, b) {

// ... implement timing-safe string comparison ...

return areEqual;

}

5. Monitoring Deliveries

Gridaly keeps a log of every attempt to deliver a notification to your webhook URL. You can view these logs in the Webhook settings area.

The Delivery Logs table shows:

  • Event Type: The type of event that triggered the delivery.

  • Status: The outcome of the delivery attempt:

    • Pending: The delivery is scheduled but not yet attempted.

    • Success: Your endpoint received the request and responded with an HTTP status code 2xx.

    • Failed: The delivery attempt failed (e.g., network error, timeout, or your endpoint responded with a non-2xx status code).

  • Response Code: The HTTP status code returned by your endpoint (if a response was received).

  • Attempt: The attempt number for this specific notification (1 for the first try, 2 for the first retry, etc.).

  • Processed At: The date and time when the delivery attempt was completed.

Reviewing these logs is essential for troubleshooting delivery issues. If you see persistent failures, check your endpoint's availability, logs, and ensure it's correctly handling requests and returning 2xx status codes upon successful processing.

6. Troubleshooting

  • Persistent Failures: Check the Delivery Logs for error messages or response codes.

    • Ensure your URL is correct and publicly accessible.

    • Verify your server/application is running and not returning errors (check your server logs).

    • Check firewalls or security groups that might be blocking requests from Gridaly.

    • Ensure your endpoint responds with a 2xx status code within a reasonable time (Gridaly's timeout is typically around 10 seconds).

  • Signature Verification Failing: Double-check that you are using the correct Secret Key copied from the Gridaly webhook settings. Ensure you are computing the HMAC-SHA256 hash on the raw request body, not a parsed version.

  • Not Receiving Events: Confirm the webhook is "Active". Verify you have subscribed to the correct "Event Types". If using event filtering, ensure the event triggering the webhook matches the selected events.

7. Available Webhook Events

7.1. Attendee Events

Event Type

Trigger

Description

attendee.created

New attendee is created

Sent when a new attendee is registered for an event

attendee.updated

Attendee data is modified

Sent when attendee information is updated (profile, status, settings, etc.)

attendee.deleted

Attendee is removed

Sent when an attendee is deleted from the system

attendee.checkin

Attendee checks in

Sent when an attendee is checked in at the event (uses a dedicated, smaller payload)

Attendee Webhook Payload Structure:

The data object for attendee.created, attendee.updated and attendee.deleted events contains:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

"additionalEmail": "john.alternate@example.com",

"activity": "active",

"status": "confirmed",

"active": true,

"public": true,

"virtualPerson": false,

"phone": "+1234567890",

"order": 1,

"lang": "en",

"country": "US",

"externalId": "EXT-12345",

"externalTicket": "TKT-98765",

"firstname": "John",

"surname": "Doe",

"headline": "Software Engineer",

"website": "https://johndoe.com",

"pphone": "+1234567890",

"biography": "Experienced software engineer...",

"location": "New York, USA",

"company": "Tech Corp",

"position": "Senior Developer",

"timezone": "America/New_York",

"activedAt": "2024-01-15T10:30:00Z",

"checkinAt": "2024-01-20T09:00:00Z",

"firstAt": "2024-01-15T10:35:00Z",

"activityWebAt": "2024-01-15T10:30:00Z",

"activityMobileAt": "2024-01-16T08:00:00Z",

"activityWebApp": "active",

"activityMobileApp": "active",

"disableTransmissionReactions": false,

"gamificationShowInRanking": true,

"gamificationNotifyEndedTask": true,

"meetingsNotificationsViaEmail": true,

"groupsNotificationsViaEmail": true,

"doNotDisturb": false,

"receiveMeetingProposals": true,

"autoAcceptMeetingInvitations": false,

"autoCreateExhibitorScanForLead": true,

"pushNotifications": true,

"canBeVoted": true,

"headlinePattern": "default",

"showOnNetworkingList": true,

"marketingConsent": true,

"ticket": {

"id": "ticket-uuid",

"name": "VIP Pass"

},

"addonables": [],

"exhibitors": [],

"roles": [

{

"id": "role-uuid",

"name": "Speaker"

}

],

"tags": [

{

"id": "tag-uuid",

"name": "Technology"

}

],

"twitter": "@johndoe"

}

Attendee Check-in Payload Structure:

The attendee.checkin event uses a dedicated, smaller data object:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

"firstname": "John",

"surname": "Doe",

"ticket": {

"id": "ticket-uuid",

"name": "VIP Pass"

},

"checkinAt": "2024-01-20T09:00:00Z",

"checkinBy": "Anna Kowalska",

"createdAt": "2024-01-15T10:30:00Z"

}

7.2. Order Events

Event Type

Trigger

Description

order.created

New order is placed

Sent when a new order is created in the system

order.updated

Order data is modified

Sent when order information is updated (status, buyer data, invoice data, etc.)

order.deleted

Order is removed

Sent when an order is deleted from the system

Order Webhook Payload Structure:

The data object for order events contains:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

"eid": "ORD-2024-001234",

"externalId": "EXT-ORD-5678",

"status": "completed",

"firstname": "Jane",

"surname": "Smith",

"customFields": {

"dietary_preferences": "vegetarian",

"company_size": "50-100"

},

"invoiceType": "company",

"invoiceFirstname": "Jane",

"invoiceSurname": "Smith",

"invoiceCompany": "Smith & Co",

"invoiceTaxId": "1234567890",

"invoiceStreet": "Main Street",

"invoiceHouse": "123",

"invoiceApartment": "4B",

"invoicePostalCode": "10001",

"invoiceCity": "New York",

"invoiceCountry": "US",

"language": "en",

"timezone": "America/New_York",

"eventId": "event-uuid",

"events": [

"event-uuid-1",

"event-uuid-2"

],

"createdAt": "2024-01-15T10:30:00Z",

"updatedAt": "2024-01-15T11:00:00Z"

}

7.3. Payment Events

Event Type

Trigger

Description

payment.created

New payment is initiated

Sent when a new payment record is created

payment.updated

Payment status or data changes

Sent when payment information is updated (status, approval, rejection, etc.)

payment.deleted

Payment is removed

Sent when a payment is deleted from the system

Payment Webhook Payload Structure:

The data object for payment events contains:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

"eid": "PAY-2024-001234",

"number": "INV/2024/001234",

"kind": "invoice",

"externalId": "STRIPE-ch_1234567890",

"amount": 15000,

"currency": "USD",

"paymentStatus": "paid",

"paymentMethod": "card",

"paymentProvider": "stripe",

"paymentDays": 14,

"paymentItems": [

{

"name": "VIP Pass",

"type": "ticket",

"quantity": 2,

"netTotal": 12195.12,

"vatTotal": 2804.88,

"grossTotal": 15000

}

],

"ticketsQuantity": 2,

"addonsQuantity": 0,

"invoiceType": "company",

"invoiceFirstname": "Jane",

"invoiceSurname": "Smith",

"invoiceCompany": "Smith & Co",

"invoiceTaxId": "1234567890",

"invoiceStreet": "Main Street",

"invoiceHouse": "123",

"invoiceApartment": "4B",

"invoicePostalCode": "10001",

"invoiceCity": "New York",

"invoiceCountry": "US",

"invoiceAlert": null,

"vatFree": false,

"commentBuyer": "Please send invoice to accounting department",

"commentOrganiser": "VIP client - priority processing",

"serviceRealized": true,

"voucherId": "voucher-uuid",

"orderId": "order-uuid",

"eventId": "event-uuid",

"events": [

"event-uuid-1",

"event-uuid-2"

],

"createdAt": "2024-01-15T10:30:00Z",

"updatedAt": "2024-01-15T11:00:00Z",

"expiredAt": "2024-01-29T23:59:59Z",

"approvedAt": "2024-01-15T11:00:00Z",

"rejectedAt": null

}

7.4. Accounting Document Events

Gridaly also sends webhooks for accounting documents issued in the system: advances, corrections, invoices, proformas, and receipts. All document types share a common base payload structure, extended with a few type-specific fields.

Event Type

Trigger

Description

advance.created

Advance invoice is created

Sent when a new advance invoice is created

advance.updated

Advance invoice is modified

Sent when advance invoice data changes (status, numbering, payment, etc.)

correction.created

Correction is created

Sent when a correction of an invoice, advance or receipt is created

correction.updated

Correction is modified

Sent when correction data changes

invoice.created

Invoice is created

Sent when a new invoice is created

invoice.updated

Invoice is modified

Sent when invoice data changes (status, numbering, payment, etc.)

proforma.created

Proforma is created

Sent when a new proforma is created

proforma.updated

Proforma is modified

Sent when proforma data changes

receipt.created

Receipt is created

Sent when a new receipt is created

receipt.updated

Receipt is modified

Sent when receipt data changes

Common Document Payload Structure:

The data object for all accounting document events contains the following base fields:

{

"id": "123e4567-e89b-12d3-a456-426614174000",

"eid": "INV-2024-001234",

"number": "FV/01/2024/0001",

"status": 2,

"netTotal": 12195.12,

"vatTotal": 2804.88,

"grossTotal": 15000,

"currency": "USD",

"languages": [

"en",

"pl"

],

"alert": null,

"vatFree": false,

"vatExemption": null,

"NBPExchangeRate": null,

"items": [

{

"name": "VIP Pass",

"type": "ticket",

"quantity": 2,

"netTotal": 12195.12,

"vatTotal": 2804.88,

"grossTotal": 15000

}

],

"ticketsQuantity": 2,

"addonsQuantity": 0,

"buyerType": "company",

"buyerFirstname": "Jane",

"buyerSurname": "Smith",

"buyerCompany": "Smith & Co",

"buyerTaxId": "1234567890",

"buyerStreet": "Main Street",

"buyerHouse": "123",

"buyerApartment": "4B",

"buyerPostalCode": "10001",

"buyerCity": "New York",

"buyerCountry": "US",

"sellerCompany": "Event Organizer Ltd",

"sellerTaxId": "0987654321",

"system": "gridaly",

"integration": null,

"orderId": "order-uuid",

"paymentId": "payment-uuid",

"eventId": "event-uuid",

"events": [

"event-uuid-1",

"event-uuid-2"

],

"invoiceAt": "2024-01-15T10:30:00Z",

"exchangeAt": null,

"createdAt": "2024-01-15T10:30:00Z",

"updatedAt": "2024-01-15T11:00:00Z"

}

The integration object depends on the accounting system the document is issued in:

  • For fakturownia and inFakt: { "id": "external-document-id" }

  • For ksef: { "number": "ksef-number", "referenceNumber": "ksef-reference-number" }

  • For gridaly: null (the document is issued in Gridaly itself)

Type-Specific Fields:

In addition to the base fields above, each document type adds:

  • Invoice (invoice.*): paymentStatus, paymentDays, advances (UUIDs of advances settled by the invoice), soldAt, paidAt

  • Advance (advance.*): paymentStatus, paymentDays, corrections (UUIDs of corrections of the advance), issuePlannedAt, soldAt, paidAt

  • Receipt (receipt.*): paymentStatus, paymentDays, advances (UUIDs of advances settled by the receipt), soldAt, paidAt

  • Proforma (proforma.*): paymentStatus, paymentDays

  • Correction (correction.*): kind (valueCorrection or quantitiveCorrection), correctableId and correctableType (UUID and type of the corrected document: invoice, advance or receipt), correctionId (UUID of a newer correction superseding this one, if any), refundId (UUID of the related refund, if any), soldAt, dueAt

8. Best Practices

  • Use HTTPS: Always use https:// URLs for your webhook endpoints to encrypt data in transit.

  • Verify Signatures: Always verify the X-Gridaly-Signature to ensure security.

  • Respond Quickly: Acknowledge receipt of the webhook by returning a 2xx HTTP status code as quickly as possible (e.g., within 2-3 seconds). Perform complex processing asynchronously (e.g., using a background job queue) to avoid timeouts.

  • Handle Retries Gracefully: Your endpoint might receive the same notification multiple times due to retries. Design your processing logic to be idempotent (i.e., processing the same notification multiple times doesn't cause unintended side effects). You can use the X-Gridaly-Delivery header (unique per attempt) or the id within the payload to detect duplicates.

  • Monitor Your Endpoint: Keep an eye on your application's logs and performance to ensure it can handle the webhook traffic.

Czy to odpowiedziało na twoje pytanie?