Webhooks

Topics covered on this page

Webhooks

Overview

Webhooks are HTTP POST requests sent from our servers to an HTTPS endpoint of your choice when specific events occur on your account. Each request includes an event object containing a data payload for the triggering action.

Webhooks enable asynchronous, real-time notification when important events occur on your account. Instead of repeatedly polling the API to check for status changes, webhooks push notifications directly to your server the moment an event happens.

Key benefits:

  • Efficiency: Eliminates the need for continuous polling, reducing unnecessary API calls and server load
  • Real-time updates: Receive instant notifications when events occur, such as successful charge completions, refunds, or disputes
  • Automation: Triggers automated workflows in your application immediately on receiving an event, such as updating order statuses, sending customer emails, or reconciling accounts
  • Reliability: Ensures you never miss critical events, even during periods when your application might not be actively checking for updates

Configuration

Configure webhook endpoints separately for test and live modes:

Viewing Event Data

Access all webhook event data through:

  • The dashboard under Events. Click the Account icon on the right pane and select Events.
  • The Events API

Serialization

The event object in the POST request (or returned via the Events API) is always serialized according to the default API Version as of the event, regardless of the Omise-Version header value sent in the triggering request.

Example

For an account set at version 2017-11-02:

  1. A charge is created using curl with -H "Omise-Version: 2019-05-29"
  2. The request generates a charge.create event with an embedded charge object
  3. The object is serialized according to version 2017-11-02 of the Charge API, not version 2019-05-29
  4. Even after updating your account to version 2019-05-29, the event remains serialized according to version 2017-11-02

Requirements and Best Practices

Technical Requirements

  • URLs must use HTTPS
  • URLs must use a valid SSL certificate (self-signed certificates are not supported)
  • Verify your SSL certificate using SSL Labs
  • For free SSL certificates, visit Let's Encrypt

Security Best Practices

Recommended: Signature Verification

We strongly recommend implementing signature verification as the primary security measure to ensure the authenticity of webhooks. Signature verification cryptographically confirms that requests originate from Omise and have not been tampered with, providing the strongest protection against malicious actors attempting to send fraudulent webhooks to your endpoint. Refer to the Protecting Your Endpoints section for implementation details.

Alternative: Event Verification

If signature verification is not feasible for your implementation, use event verification as an alternative. Upon receiving a webhook event (e.g., charge.complete), use the resource ID to perform an independent GET request to confirm the status. Note that this method does not prevent your endpoint from receiving fraudulent requests; it only enables you to verify the authenticity of the event data after it has been received.

Example event verification workflow:

1. Receive `charge.complete` webhook
2. Extract Charge ID from the webhook payload
3. Perform GET request to `/charges/chrg_test_no1t4tnemucod0e51mo`
4. Verify the charge status independently

Supported Events

Card Events

Event Name Trigger
card.destroy Card has been destroyed
card.update Card has been updated

Charge Events

Event Name Trigger
charge.capture Charge has been captured (manual capture only)
charge.complete Charge has been completed successfully (Note: completing non-3DS card charges does not trigger this webhook)
charge.create Charge has been created
charge.expire Charge has expired (Barcode Alipay only)
charge.reverse Charge has been reversed (manual reversal only)
charge.update Charge has been updated

Customer Events

Event Name Trigger
customer.create Customer has been created
customer.destroy Customer has been destroyed
customer.update Customer has been updated
customer.update.card Card has been updated implicitly through a customer

Dispute Events

Event Name Trigger
dispute.accept Dispute has been accepted
dispute.close Dispute has been closed
dispute.create Dispute has been opened
dispute.update Dispute has been updated
Event Name Trigger
link.create Link has been created

Linked Account Events

Event Name Trigger
linked_account.create Linked account has been created
linked_account.complete Linked account has been registered by the customer

Recipient Events

Event Name Trigger
recipient.activate Recipient has been activated
recipient.create Recipient has been created
recipient.deactivate Recipient has been deactivated
recipient.destroy Recipient has been destroyed
recipient.update Recipient has been updated
recipient.verify Recipient has been verified

Refund Events

Event Name Trigger
refund.create Refund has been created

Schedule Events

Event Name Trigger
schedule.create Schedule has been created
schedule.destroy Schedule has been destroyed
schedule.expire Schedule has expired
schedule.expiring Schedule will expire soon
schedule.suspend Schedule has been suspended

Transfer Events

Event Name Trigger
transfer.create Transfer has been created
transfer.destroy Transfer has been destroyed
transfer.fail Transfer has been marked as failed
transfer.pay Transfer has been marked as paid
transfer.send Transfer has been marked as sent
transfer.update Transfer has been updated

Dynamic Webhooks

All Omise accounts include a single configurable static webhook endpoint. By default, notifications for all events are sent to this endpoint.

The dynamic webhooks feature allows you to specify custom webhook endpoints on a per-charge basis by passing the webhook_endpoints parameter when creating a charge.

Behavior

When webhook_endpoints is specified:

  • All events for the specific charge are sent to the specified webhook_endpoints
  • Events are not sent to the static webhook

When webhook_endpoints is not specified:

  • Event notifications are sent to the account's static webhook

Protecting Your Endpoints

Omise uses the HMAC-SHA256 algorithm to sign webhook payloads cryptographically. When a webhook secret is configured for your account, a digital signature is included in the webhook headers. Verify the signature to ensure the authenticity and integrity of webhook requests.

Webhook Signature Headers

Header Description
Omise-Signature Hex-encoded HMAC signature generated by Omise. During secret rotation, the header contains two comma-separated signatures, each corresponding to one of the secrets.
Omise-Signature-Timestamp Unix timestamp when the signature was generated

Example headers:

Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83
Omise-Signature-Timestamp: 1758696391

// During rotation
Omise-Signature: 072cc0d8b49d49ce7119857279b1e36a9efa25fadb468d0126628064d4062c83,4bb1e14023f53d076e598245e5e16fd96a94d46072149c483815718eecf2a38d

How to Verify Webhook Signatures

Step 1: Retrieve the Signatures

Extract the Omise-Signature and Omise-Signature-Timestamp headers from the webhook request.

Step 2: Prepare the Signed Payload

Construct the payload for HMAC signature generation by concatenating:

  1. The value of the Omise-Signature-Timestamp header
  2. A dot (.)
  3. The UTF-8 encoded raw webhook request body

Format:

<TIMESTAMP>.<RAW_BODY>

Step 3: Decode the Webhook Secret

The webhook secret is a Base64-encoded HMAC secret key. Decode it before proceeding to the next step.

Step 4: Compute the Expected Signature

Compute the HMAC-SHA256 digest of the signed payload using your decoded secret, then encode the result as a hexadecimal string.

Step 5: Compare the Signatures

  1. Iterate over all signatures in the Omise-Signature header
  2. Compare each signature against the expected signature
  3. Reject the webhook request if none of the signatures match
  4. If a signature matches, you may proceed with processing the webhook immediately, or optionally validate the timestamp first for additional security against replay attacks

Important security considerations:

  • Timing attack protection: Use constant-time string comparison algorithms when comparing signatures to prevent timing attacks
  • Replay attack protection (optional): For enhanced security, validate timestamps by computing the difference between your current system time and the received timestamp. Reject webhooks if the difference exceeds your acceptable window (e.g., 5 minutes) to prevent replay attacks. Timestamp validation is optional; a successful signature match alone confirms the webhook is authentic.

Example Implementation

const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf;
  }
}));

function verifySignature({ req }) {
  // Step 1: Retrieve the signature(s)
  const signatureHeader = req.headers['omise-signature'];
  const timestampHeader = req.headers['omise-signature-timestamp'];

  // Step 2: Prepare the signed payload
  const rawBody = req.rawBody.toString('utf8');
  const signedPayload = `${timestampHeader}.${rawBody}`;

  // Step 3: Decode the webhook secret
  const secret = Buffer.from('<YOUR_WEBHOOK_SECRET>', 'base64');

  // Step 4: Compute the expected signature
  const expectedBuffer = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest();

  // Step 5: Compare against all signatures in the header
  const signatures = signatureHeader.split(',');
  for (const sig of signatures) {
    const sigBuffer = Buffer.from(sig, 'hex');
    if (crypto.timingSafeEqual(sigBuffer, expectedBuffer)) {
      // Optional: Add timestamp validation here
      return true;
    }
  }
  return false;
}

app.post('/webhook', (req, res) => {
  if (!verifySignature(req)) {
     res.status(401).send('Invalid signature')
  }

  // If verified, return 200 after processing webhook
  res.status(200).send('OK');
});

Managing Webhook Secrets

You can view, generate, and manage your webhook secrets in the Webhooks Settings section of your Omise dashboard. Secrets are environment-specific; your Test Mode secret is independent of your Live Mode secret.
[CAUTION] Webhook Secrets are confidential. Treat them as sensitive credentials. Never expose them in client-side code, public repositories, or share them in insecure environments.

Secret Rotation

If you need to update your secret (e.g., for security compliance or because a secret was leaked), Omise provides a zero-downtime rotation path:

  • Rotating a Secret: When you Roll a secret, a new one is generated immediately. To prevent service interruption, the previous secret remains valid for 24 hours.
  • Dual-Signatures: During this 24-hour transition, Omise signs every webhook with both the new and the old secret. The Omise-Signature header will contain two comma-separated signatures.
  • Manual Revocation: You cannot delete your primary active secret. However, you can manually revoke an expiring secret at any time during the 24-hour window to end the transition period immediately.
  • Capacity: You can have a maximum of two secrets active at once (one current, one expiring). If an expiring secret is still active, you must wait for it to lapse or manually revoke it before you can roll your secret again.

Testing

We recommend using Test Mode to validate your signature verification logic before switching to Live Mode.

  • Safe Simulation: Rotating or revoking secrets in Test Mode has no impact on your Live production environment.
  • Verify Rotation Logic: We strongly recommend testing the Secret Rotation flow in Test Mode. This ensures your code correctly handles the Dual-Signature behavior before you perform a rotation in your production environment.

Troubleshooting

Common Issues

Webhook not received:

  • Verify your endpoint URL is HTTPS with a valid SSL certificate
  • Check that your firewall allows incoming requests from Omise servers
  • Confirm your endpoint is accessible and responding to requests

Signature verification fails:

  • Confirm you are using the correct webhook secret for your environment (test vs. live)
  • Verify you are using the raw request body without any modifications
  • Ensure proper Base64 decoding of the webhook secret

Events sent to the wrong endpoint:

  • Check if the webhook_endpoints parameter was specified during charge creation
  • Verify static webhook configuration in the dashboard

Additional Resources

Omise uses cookies to improve your overall site experience and collect information on your visits and browsing behavior. By continuing to browse our website, you agree to our Privacy Policy. Learn more