Jump to content

Webhook information?


BillOatman
 Share

Recommended Posts

Hi, I've been looking at the wiki and forum for information on how to implement a webhook endpoint.  Primarily what information is actually sent, and how (get/post/put) it is sent to the endpoint. Is there information somewhere on this?

Thanks!

Edited by BillOatman
Link to comment
Share on other sites

You could use a dummy webhook to see what parameters Emby sends using a site such as https://webhook.site/ 

Once you've got a dummy webhook from the site, you can simply add a new webhook in the Emby webhooks plugin, enable all the events, and for each event do an action that would result in that event being emitted (i.e. playing a video)

  • Thanks 1
Link to comment
Share on other sites

6 hours ago, rechigo said:

You could use a dummy webhook to see what parameters Emby sends using a site such as https://webhook.site/ 

Once you've got a dummy webhook from the site, you can simply add a new webhook in the Emby webhooks plugin, enable all the events, and for each event do an action that would result in that event being emitted (i.e. playing a video)

That's a great tip, thanks. I've been meaning to update the web hooks config pages to show the contents of a sample payload.

Link to comment
Share on other sites

Thanks guys.  I configured a webhook in Emby last night but when sending the test message it got an error about a invalid URL.  I have the info from the log at home and will post later.  But all I had for the url was

http://localhost:12345/api/test

I also tried with the IP address of the Emby server instead of localhost and got the same error. How is the url supposed to be specified?

Link to comment
Share on other sites

You'd have to look at the log. The url might be fine, but maybe the server got an error response back, or couldn't connect at all to it.

Link to comment
Share on other sites

@Luke  Here is the data from the log.

The top one is when I used localhost and the bottom when I used the IP address.  The endpoint is on the same box as the Emby server.

2021-03-18 18:29:55.975 Info HttpClient: POST http://localhost:12345/api/blah
2021-03-18 18:29:55.979 Error HttpClient: {"Message":"The request entity's media type 'multipart/form-data' is not supported for this resource.","ExceptionMessage":"No MediaTypeFormatter is available to read an object of type 'String' from content with media type 'multipart/form-data'.","ExceptionType":"System.Net.Http.UnsupportedMediaTypeException","StackTrace":"   at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)"}
2021-03-18 18:29:56.043 Error Server: Error processing request
	*** Error Report ***
	Version: 4.5.4.0
	Command line: C:\Users\woatm\AppData\Roaming\Emby-Server\system\EmbyServer.dll -noautorunwebapp
	Operating system: Microsoft Windows 10.0.19042
	Framework: .NET Core 3.1.9
	OS/Process: x64/x64
	Runtime: C:/Users/woatm/AppData/Roaming/Emby-Server/system/System.Private.CoreLib.dll
	Processor count: 12
	Data path: C:\Users\woatm\AppData\Roaming\Emby-Server\programdata
	Application path: C:\Users\woatm\AppData\Roaming\Emby-Server\system
	MediaBrowser.Model.Net.HttpException: MediaBrowser.Model.Net.HttpException: {"Message":"The request entity's media type 'multipart/form-data' is not supported for this resource.","ExceptionMessage":"No MediaTypeFormatter is available to read an object of type 'String' from content with media type 'multipart/form-data'.","ExceptionType":"System.Net.Http.UnsupportedMediaTypeException","StackTrace":"   at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n   at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)"}
	   at Emby.Server.Implementations.HttpClientManager.CoreHttpClientManager.SendAsyncInternal(HttpRequestOptions options, String httpMethod)
	   at Emby.Server.Implementations.HttpClientManager.CoreHttpClientManager.SendAsync(HttpRequestOptions options, String httpMethod)
	   at Emby.Server.Implementations.Services.ServiceController.GetTaskResult(Task task)
	   at Emby.Server.Implementations.Services.ServiceHandler.ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, RestPath restPath, String responseContentType, CancellationToken cancellationToken)
	   at Emby.Server.Implementations.HttpServer.HttpListenerHost.RequestHandler(IRequest httpReq, ReadOnlyMemory`1 urlString, ReadOnlyMemory`1 localPath, CancellationToken cancellationToken)
	Source: Emby.Server.Implementations
	TargetSite: Void MoveNext()
	
	________________________
	
	
2021-03-18 18:35:08.047 Info HttpClient: POST http://192.168.50.42:12345/api/blah
2021-03-18 18:35:08.049 Error HttpClient: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Bad Request</TITLE>
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
<BODY><h2>Bad Request - Invalid Hostname</h2>
<hr><p>HTTP Error 400. The request hostname is invalid.</p>
</BODY></HTML>

2021-03-18 18:35:08.050 Error Server: Error processing request
	*** Error Report ***
	Version: 4.5.4.0
	Command line: C:\Users\woatm\AppData\Roaming\Emby-Server\system\EmbyServer.dll -noautorunwebapp
	Operating system: Microsoft Windows 10.0.19042
	Framework: .NET Core 3.1.9
	OS/Process: x64/x64
	Runtime: C:/Users/woatm/AppData/Roaming/Emby-Server/system/System.Private.CoreLib.dll
	Processor count: 12
	Data path: C:\Users\woatm\AppData\Roaming\Emby-Server\programdata
	Application path: C:\Users\woatm\AppData\Roaming\Emby-Server\system
	MediaBrowser.Model.Net.HttpException: MediaBrowser.Model.Net.HttpException: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
	<HTML><HEAD><TITLE>Bad Request</TITLE>
	<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD>
	<BODY><h2>Bad Request - Invalid Hostname</h2>
	<hr><p>HTTP Error 400. The request hostname is invalid.</p>
	</BODY></HTML>
	
	   at Emby.Server.Implementations.HttpClientManager.CoreHttpClientManager.SendAsyncInternal(HttpRequestOptions options, String httpMethod)
	   at Emby.Server.Implementations.HttpClientManager.CoreHttpClientManager.SendAsync(HttpRequestOptions options, String httpMethod)
	   at Emby.Server.Implementations.Services.ServiceController.GetTaskResult(Task task)
	   at Emby.Server.Implementations.Services.ServiceHandler.ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, RestPath restPath, String responseContentType, CancellationToken cancellationToken)
	   at Emby.Server.Implementations.HttpServer.HttpListenerHost.RequestHandler(IRequest httpReq, ReadOnlyMemory`1 urlString, ReadOnlyMemory`1 localPath, CancellationToken cancellationToken)
	Source: Emby.Server.Implementations
	TargetSite: Void MoveNext()	

I'm guessing it wants the ip address and not localhost, but why it the bottom one  invalid?

HTTP Error 400. The request hostname is invalid.

 

Edited by BillOatman
Link to comment
Share on other sites

Whatever code is receiving the request is rejecting with a 400 response because it doesn't like the value of the host http request header...possibly due to it being empty. So I would look at that.

Link to comment
Share on other sites

On 3/20/2021 at 10:02 PM, Luke said:

Whatever code is receiving the request is rejecting with a 400 response because it doesn't like the value of the host http request header...possibly due to it being empty. So I would look at that.

Not even content-type??

Is there an example of the post payload for the test message and the playback type?
I am trying to use the C# apicontroller to self host the receiver and it is a bit particular about what is sent, particularly if its json.

Link to comment
Share on other sites

Quote

Is there an example of the post payload for the test message and the playback type?

It's something I've been meaning to add right into the config screen.

Link to comment
Share on other sites

For others ... this is what gets sent for the test button

Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=data

{"Event":"system.webhooktest","Server":{"Name":"Emby Server","Id":"id-characters"}}

 

  • Thanks 1
Link to comment
Share on other sites

  • 11 months later...
Loefamily

I recognize this thread is 1 year old, however, I found it in my search for more detailed information about Emby webhooks and the data it POSTs to the configured endpoint.  I took some time to learn (for fun) Node.js, Express, and Mongoose to standup a simple API with the idea of simply logging all the events in MongoDB. Later I would decide what I want to do with them or any actions I wanted to process as they come in.  Here are a couple things I learned.

A. There are several events published by Webhooks (System, Playback, and User events) but I am hoping more are added such as:

1. Recording Events

  • Recording Set/Canceled
  • Recording Started/Completed/Failed
  • DVR Post recording script result (return code)

2. Guide Events

  • Guide data refreshed
  • Series recordings - new occurrence found/scheduled

 B. Node Express prefers JSON out of the box

Emby POSTs as mentioned by @BillOatman. The key implication is that typical route processing in Node Express won't pick up the form-data as part of req.body (request body). This means you have to tweak a few things, namely using 'multer' module. 
Ultimately, I ended up with:

// routes/emby-event.js
// Disclaimer: Clearly not production code!
//
let EmbyEventModel = require('../models/emby-event.model')
let express = require('express')
let router = express.Router()
let multer = require('multer') // Required for form-data submissions
let upload = multer()

// CREATE a new emby-event
router.post('/emby-event', upload.none(), (req, res) => {
    
    if(req.body) {
        console.log('Using JSON in request body')
        eventData =  JSON.parse(req.body.data)
    } else {
        console.error('Request body not found!')
        res.status(400).send('Request body is missing')
    }
// ...
    let model = new EmbyEventModel(eventData, false)
    model.save()
        .then(doc => {
            if(!doc || doc.length === 0) {
                console.log('error saving document')
                return res.status(500).send(doc)
            }
            res.status(201).send(doc)
        })
        .catch(err => {
            console.error('caught an error while trying to save', err)
            res.status(500).json(err)
        })
})

 

C. Event Schema (relevant for Mongoose Schema and/or reference purposes):
Once I was able to log the data submitted by Emby, I was able to parse it and convert the raw values to the Schema types supported by mongoose module. This is not 'strictly' required but ensures that mongoose preserves the values sent. Note: you could instead rely on an empty Schema {} with { strict false}.  Of course, I find this useful for reference as to what information is sent from Emby.  Note the Schema below was sourced from raw data received for event type 'playback.start'. I haven't compared all the event types yet to know if they are consistent.

// models/emby-event.model.js
// Disclaimer: Clearly not production code!
//
let mongoose = require ('mongoose')
const eventdb = {
  server :'localhost:27017',
  database : 'webhook-api',
  user : '***',
  password : '***'
}

conn = mongoose.createConnection(`mongodb://${eventdb.user}:${eventdb.password}@${eventdb.server}/${eventdb.database}`, {
    authSource: 'admin'
})

let EmbyEventSchema = new mongoose.Schema( {
    Event: String,
    User: {
        Name: String,
        ServerId: String,
        ConnectUserName: String,
        ConnectLinkType: String,
        Id: String,
        PrimaryImageTag: String,
        HasPassword: Boolean,
        HasConfiguredPassword: Boolean,
        HasConfiguredEasyPassword: Boolean,
        LastLoginDate: Date,
        LastActivityDate: Date,
        Configuration: {
            AudioLanguagePreference: String,
            PlayDefaultAudioTrack: Boolean,
            SubtitleLanguagePreference: String,
            DisplayMissingEpisodes: Boolean,
            SubtitleMode: String,
            EnableLocalPassword: Boolean,
            OrderedViews: Array,
            LatestItemsExcludes: Array,
            MyMediaExcludes: Array,
            HidePlayedInLatest: Boolean,
            RememberAudioSelections: Boolean,
            RememberSubtitleSelections: Boolean,
            EnableNextEpisodeAutoPlay: Boolean
        },
        Policy: {
            IsAdministrator: Boolean,
            IsHidden: Boolean,
            IsHiddenRemotely: Boolean,
            IsHiddenFromUnusedDevices: Boolean,
            IsDisabled: Boolean,
            BlockedTags: Array,
            IsTagBlockingModeInclusive: Boolean,
            EnableUserPreferenceAccess: Boolean,
            AccessSchedules: Array,
            BlockUnratedItems: Array,
            EnableRemoteControlOfOtherUsers: Boolean,
            EnableSharedDeviceControl: Boolean,
            EnableRemoteAccess: Boolean,
            EnableLiveTvManagement: Boolean,
            EnableLiveTvAccess: Boolean,
            EnableMediaPlayback: Boolean,
            EnableAudioPlaybackTranscoding: Boolean,
            EnableVideoPlaybackTranscoding: Boolean,
            EnablePlaybackRemuxing: Boolean,
            EnableContentDeletion: Boolean,
            EnableContentDeletionFromFolders: Array,
            EnableContentDownloading: Boolean,
            EnableSubtitleDownloading: Boolean,
            EnableSubtitleManagement: Boolean,
            EnableSyncTranscoding: Boolean,
            EnableMediaConversion: Boolean,
            EnabledChannels: Array,
            EnableAllChannels: Boolean,
            EnabledFolders: Array,
            EnableAllFolders: Boolean,
            InvalidLoginAttemptCount: Number,
            EnablePublicSharing: Boolean,
            RemoteClientBitrateLimit: Number,
            AuthenticationProviderId: String,
            ExcludedSubFolders: Array,
            SimultaneousStreamLimit: Number,
            EnabledDevices: Array,
            EnableAllDevices: Boolean
        },
        PrimaryImageAspectRatio: mongoose.Decimal128
  },
  Item: {
    Name: String,
    ServerId: String,
    Id: String,
    DateCreated: Date,
    PresentationUniqueKey: String,
    Container: String,
    PremiereDate: Date,
    ExternalUrls: Array,
    Path: String,
    Overview: String,
    Taglines: Array,
    Genres: Array,
    RunTimeTicks: Number,
    ProductionYear: Number,
    ProviderIds: mongoose.Mixed,
    IsFolder: Boolean,
    ParentId: String,
    Type: String,
    Studios: Array,
    GenreItems: Array,
    SeriesName: String,
    SeriesId: String,
    SeasonId: String,
    PrimaryImageAspectRatio: mongoose.Decimal128,
    SeasonName: String,
    MediaStreams: [mongoose.Mixed],
    ImageTags: { Primary: String },
    BackdropImageTags: Array,
    Chapters: [mongoose.Mixed],
    MediaType: String,
    Width: Number,
    Height: Number
  },
  Server: { Name: String, Id: String },
  Session: {
    RemoteEndPoint: String,
    Client: String,
    DeviceName: String,
    DeviceId: String,
    ApplicationVersion: String,
    Id: String
  }
}, 
{
    strict: false 
})

module.exports = conn.model('Event', EmbyEventSchema)

 

  • Thanks 1
Link to comment
Share on other sites

Looking at this, the User info is excessive for what webhooks is trying to accomplish. I'm going to make that smaller. If you need the full user object you can always pull it from the api.

  • Like 1
Link to comment
Share on other sites

Also here's some info on new data that will be added to webhooks: 

 

  • Thanks 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...